blob: 5547c3c709bcb5f85fccbdbe7ea9c3268029311b [file] [log] [blame]
Constantin Kaplinsky903009e2002-05-20 10:55:47 +00001//
2// Copyright (C) 2002 HorizonLive.com, Inc. All Rights Reserved.
3//
4// This is free software; you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation; either version 2 of the License, or
7// (at your option) any later version.
8//
9// This software is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this software; if not, write to the Free Software
16// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
17// USA.
18//
19
20//
21// FbsInputStream.java
22//
23
wimba.comc23aeb02004-09-16 00:00:00 +000024package com.HorizonLive.RfbPlayer;
25
Constantin Kaplinsky903009e2002-05-20 10:55:47 +000026import java.io.*;
Constantin Kaplinsky5a7133a2002-07-24 17:02:04 +000027import java.util.*;
Constantin Kaplinsky903009e2002-05-20 10:55:47 +000028
29class FbsInputStream extends InputStream {
30
31 protected InputStream in;
32 protected long startTime;
33 protected long timeOffset;
Constantin Kaplinsky52c48242002-05-30 13:26:34 +000034 protected long seekOffset;
Constantin Kaplinskyd8770f62002-08-16 16:01:37 +000035 protected boolean seekBackwards;
Constantin Kaplinskyba0c4df2002-05-30 12:23:25 +000036 protected boolean paused;
Constantin Kaplinskyce39d3b2002-05-30 15:54:56 +000037 protected double playbackSpeed;
Constantin Kaplinsky903009e2002-05-20 10:55:47 +000038
39 protected byte[] buffer;
40 protected int bufferSize;
41 protected int bufferPos;
42
Constantin Kaplinsky5a7133a2002-07-24 17:02:04 +000043 protected Observer obs;
44
Constantin Kaplinsky903009e2002-05-20 10:55:47 +000045 //
46 // Constructors.
47 //
Constantin Kaplinsky903009e2002-05-20 10:55:47 +000048 FbsInputStream() throws IOException {
49 throw new IOException("FbsInputStream: no such constructor");
50 }
51
Constantin Kaplinskyac6420d2002-05-29 17:05:39 +000052 //
53 // Construct FbsInputStream object, begin playback.
54 //
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +000055 FbsInputStream(InputStream in) throws IOException {
Constantin Kaplinsky903009e2002-05-20 10:55:47 +000056 this.in = in;
57 startTime = System.currentTimeMillis();
58 timeOffset = 0;
Constantin Kaplinsky52c48242002-05-30 13:26:34 +000059 seekOffset = -1;
Constantin Kaplinskyd8770f62002-08-16 16:01:37 +000060 seekBackwards = false;
Constantin Kaplinskyba0c4df2002-05-30 12:23:25 +000061 paused = false;
Constantin Kaplinskyce39d3b2002-05-30 15:54:56 +000062 playbackSpeed = 1.0;
Constantin Kaplinsky903009e2002-05-20 10:55:47 +000063
64 byte[] b = new byte[12];
65 readFully(b);
66
67 if (b[0] != 'F' || b[1] != 'B' || b[2] != 'S' || b[3] != ' ' ||
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +000068 b[4] != '0' || b[5] != '0' || b[6] != '1' || b[7] != '.' ||
69 b[8] < '0' || b[8] > '9' || b[9] < '0' || b[9] > '9' ||
70 b[10] < '0' || b[10] > '9' || b[11] != '\n') {
Constantin Kaplinsky903009e2002-05-20 10:55:47 +000071 throw new IOException("Incorrect protocol version");
72 }
73
74 buffer = null;
75 bufferSize = 0;
76 bufferPos = 0;
77 }
78
79 //
80 // Basic methods overriding InputStream's methods.
81 //
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +000082 public int read() throws IOException {
Constantin Kaplinsky903009e2002-05-20 10:55:47 +000083 while (bufferSize == 0) {
84 if (!fillBuffer())
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +000085 return -1;
Constantin Kaplinsky903009e2002-05-20 10:55:47 +000086 }
87 bufferSize--;
88 return buffer[bufferPos++] & 0xFF;
89 }
90
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +000091 public int available() throws IOException {
Constantin Kaplinsky903009e2002-05-20 10:55:47 +000092 // FIXME: This will work incorrectly if our caller will wait until
93 // some amount of data is available when the buffer contains less
94 // data than then that. Current implementation never reads more
95 // data until the buffer is fully exhausted.
96 return bufferSize;
97 }
98
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +000099 public synchronized void close() throws IOException {
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000100 in.close();
101 in = null;
Constantin Kaplinskyac6420d2002-05-29 17:05:39 +0000102 startTime = -1;
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000103 timeOffset = 0;
Constantin Kaplinsky52c48242002-05-30 13:26:34 +0000104 seekOffset = -1;
Constantin Kaplinskyd8770f62002-08-16 16:01:37 +0000105 seekBackwards = false;
Constantin Kaplinskyba0c4df2002-05-30 12:23:25 +0000106 paused = false;
Constantin Kaplinskyce39d3b2002-05-30 15:54:56 +0000107 playbackSpeed = 1.0;
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000108
109 buffer = null;
110 bufferSize = 0;
111 bufferPos = 0;
Constantin Kaplinsky5a7133a2002-07-24 17:02:04 +0000112
113 obs = null;
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000114 }
115
116 //
117 // Methods providing additional functionality.
118 //
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000119 public synchronized long getTimeOffset() {
Constantin Kaplinsky5a7133a2002-07-24 17:02:04 +0000120 long off = Math.max(seekOffset, timeOffset);
121 return (long)(off * playbackSpeed);
Constantin Kaplinskyfe079832002-05-29 00:52:32 +0000122 }
123
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000124 public synchronized void setTimeOffset(long pos) {
Constantin Kaplinskyce39d3b2002-05-30 15:54:56 +0000125 seekOffset = (long)(pos / playbackSpeed);
Constantin Kaplinskyd8770f62002-08-16 16:01:37 +0000126 if (seekOffset < timeOffset) {
127 seekBackwards = true;
128 }
Constantin Kaplinsky52c48242002-05-30 13:26:34 +0000129 notify();
Constantin Kaplinsky30f786a2002-05-29 10:59:52 +0000130 }
131
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000132 public synchronized void setSpeed(double newSpeed) {
Constantin Kaplinsky5a7133a2002-07-24 17:02:04 +0000133 long newOffset = (long)(timeOffset * playbackSpeed / newSpeed);
134 startTime += timeOffset - newOffset;
135 timeOffset = newOffset;
Constantin Kaplinskyce39d3b2002-05-30 15:54:56 +0000136 if (isSeeking()) {
137 seekOffset = (long)(seekOffset * playbackSpeed / newSpeed);
138 }
139 playbackSpeed = newSpeed;
140 }
141
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000142 public boolean isSeeking() {
Constantin Kaplinsky52c48242002-05-30 13:26:34 +0000143 return (seekOffset >= 0);
Constantin Kaplinsky30f786a2002-05-29 10:59:52 +0000144 }
145
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000146 public long getSeekOffset() {
Constantin Kaplinsky7cc77622002-09-22 12:36:20 +0000147 return (long)(seekOffset * playbackSpeed);
Constantin Kaplinskyd8770f62002-08-16 16:01:37 +0000148 }
149
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000150 public boolean isPaused() {
Constantin Kaplinskyd8770f62002-08-16 16:01:37 +0000151 return paused;
152 }
153
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000154 public synchronized void pausePlayback() {
Constantin Kaplinskyba0c4df2002-05-30 12:23:25 +0000155 paused = true;
Constantin Kaplinskyac6420d2002-05-29 17:05:39 +0000156 notify();
Constantin Kaplinsky30f786a2002-05-29 10:59:52 +0000157 }
158
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000159 public synchronized void resumePlayback() {
Constantin Kaplinskyba0c4df2002-05-30 12:23:25 +0000160 paused = false;
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000161 startTime = System.currentTimeMillis() - timeOffset;
Constantin Kaplinskyac6420d2002-05-29 17:05:39 +0000162 notify();
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000163 }
164
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000165 public void addObserver(Observer target) {
Constantin Kaplinsky5a7133a2002-07-24 17:02:04 +0000166 obs = target;
167 }
168
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000169 //
170 // Methods for internal use.
171 //
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000172 private synchronized boolean fillBuffer() throws IOException {
Constantin Kaplinskyd8770f62002-08-16 16:01:37 +0000173 // The reading thread should be interrupted on backward seeking.
174 if (seekBackwards)
175 throw new EOFException("[REWIND]");
176
177 // Just wait unless we are performing playback OR seeking.
Constantin Kaplinskyac6420d2002-05-29 17:05:39 +0000178 waitWhilePaused();
179
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000180 bufferSize = (int)readUnsigned32();
181 if (bufferSize >= 0) {
182 int realSize = (bufferSize + 3) & 0xFFFFFFFC;
183 buffer = new byte[realSize];
184 readFully(buffer);
185 bufferPos = 0;
Constantin Kaplinskyce39d3b2002-05-30 15:54:56 +0000186 timeOffset = (long)(readUnsigned32() / playbackSpeed);
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000187 }
188
189 if (bufferSize < 0 || timeOffset < 0) {
190 buffer = null;
191 bufferSize = 0;
192 bufferPos = 0;
193 return false;
194 }
195
Constantin Kaplinsky52c48242002-05-30 13:26:34 +0000196 if (seekOffset >= 0) {
197 if (timeOffset >= seekOffset) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000198 startTime = System.currentTimeMillis() - seekOffset;
199 seekOffset = -1;
Constantin Kaplinsky5a7133a2002-07-24 17:02:04 +0000200 } else {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000201 return true;
Constantin Kaplinsky52c48242002-05-30 13:26:34 +0000202 }
Constantin Kaplinsky52c48242002-05-30 13:26:34 +0000203 }
204
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000205 while (true) {
206 long timeDiff = startTime + timeOffset - System.currentTimeMillis();
207 if (timeDiff <= 0) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000208 break;
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000209 }
Constantin Kaplinskyce39d3b2002-05-30 15:54:56 +0000210 try {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000211 wait(timeDiff);
Constantin Kaplinskyce39d3b2002-05-30 15:54:56 +0000212 } catch (InterruptedException e) {
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000213 }
Constantin Kaplinskyac6420d2002-05-29 17:05:39 +0000214 waitWhilePaused();
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000215 }
216
217 return true;
218 }
219
Constantin Kaplinskyac6420d2002-05-29 17:05:39 +0000220 //
221 // In paused mode, wait for external notification on this object.
222 //
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000223 private void waitWhilePaused() {
Constantin Kaplinsky52c48242002-05-30 13:26:34 +0000224 while (paused && !isSeeking()) {
Constantin Kaplinskyac6420d2002-05-29 17:05:39 +0000225 synchronized(this) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000226 try {
227 // Note: we call Observer.update(Observable,Object) method
228 // directly instead of maintaining an Observable object.
229 obs.update(null, null);
230 wait();
231 } catch (InterruptedException e) {
232 }
Constantin Kaplinskyac6420d2002-05-29 17:05:39 +0000233 }
234 }
235 }
236
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000237 private long readUnsigned32() throws IOException {
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000238 byte[] buf = new byte[4];
239 if (!readFully(buf))
240 return -1;
241
242 return ((long)(buf[0] & 0xFF) << 24 |
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000243 (buf[1] & 0xFF) << 16 |
244 (buf[2] & 0xFF) << 8 |
245 (buf[3] & 0xFF));
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000246 }
247
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000248 private boolean readFully(byte[] b) throws IOException {
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000249 int off = 0;
250 int len = b.length;
251
252 while (off != len) {
253 int count = in.read(b, off, len - off);
254 if (count < 0) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000255 return false;
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000256 }
257 off += count;
258 }
259
260 return true;
261 }
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000262
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000263}
264