blob: f5f4b391eb0ddee49d6ca406d802f6931d8d7e18 [file] [log] [blame]
Constantin Kaplinsky903009e2002-05-20 10:55:47 +00001//
2// Copyright (C) 2002 HorizonLive.com, Inc. All Rights Reserved.
Constantin Kaplinsky8d9c34a2008-06-19 12:02:57 +00003// Copyright (C) 2008 Wimba, Inc. All Rights Reserved.
Constantin Kaplinsky903009e2002-05-20 10:55:47 +00004//
5// This is free software; you can redistribute it and/or modify
6// it under the terms of the GNU General Public License as published by
7// the Free Software Foundation; either version 2 of the License, or
8// (at your option) any later version.
9//
10// This software is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13// GNU General Public License for more details.
14//
15// You should have received a copy of the GNU General Public License
16// along with this software; if not, write to the Free Software
17// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
18// USA.
19//
20
21//
22// FbsInputStream.java
23//
24
Constantin Kaplinskycf689b32008-04-30 12:50:34 +000025package com.tightvnc.rfbplayer;
wimba.comc23aeb02004-09-16 00:00:00 +000026
Constantin Kaplinsky903009e2002-05-20 10:55:47 +000027import java.io.*;
Constantin Kaplinsky5a7133a2002-07-24 17:02:04 +000028import java.util.*;
Constantin Kaplinsky903009e2002-05-20 10:55:47 +000029
30class FbsInputStream extends InputStream {
31
32 protected InputStream in;
33 protected long startTime;
34 protected long timeOffset;
Constantin Kaplinsky52c48242002-05-30 13:26:34 +000035 protected long seekOffset;
Constantin Kaplinsky2c0d2d12008-06-23 13:34:36 +000036 protected boolean farSeeking;
Constantin Kaplinskyba0c4df2002-05-30 12:23:25 +000037 protected boolean paused;
wimba.comd1f56df2004-11-01 16:18:54 +000038 protected boolean isQuitting = false;
Constantin Kaplinskyce39d3b2002-05-30 15:54:56 +000039 protected double playbackSpeed;
Constantin Kaplinsky903009e2002-05-20 10:55:47 +000040
41 protected byte[] buffer;
42 protected int bufferSize;
43 protected int bufferPos;
44
Constantin Kaplinsky8d9c34a2008-06-19 12:02:57 +000045 /** The number of bytes to skip in the beginning of the next data block. */
Constantin Kaplinskyd3a2df12008-06-20 20:12:22 +000046 protected long nextBlockOffset;
Constantin Kaplinsky8d9c34a2008-06-19 12:02:57 +000047
Constantin Kaplinsky5a7133a2002-07-24 17:02:04 +000048 protected Observer obs;
49
Constantin Kaplinskyd9ade182008-06-18 11:27:50 +000050 /**
Constantin Kaplinsky7fea1c42008-06-19 03:31:40 +000051 * Construct FbsInputStream object based on the given InputStream, positioned
52 * at the very beginning of the corresponding FBS file. This constructor
53 * reads and checks FBS file signature which would look like "FBS 001.000\n",
54 * but any decimal number is allowed after the dot.
Constantin Kaplinskyd9ade182008-06-18 11:27:50 +000055 *
Constantin Kaplinsky7fea1c42008-06-19 03:31:40 +000056 * @param in the InputStream object that will be used as a base for this new
57 * FbsInputStream instance. It should be positioned at the very beginning of
58 * the corresponding FBS file, so that first 12 bytes read from the stream
59 * should form FBS file signature.
60 * @throws java.io.IOException thrown on read error or on incorrect FBS file
61 * signature.
Constantin Kaplinskyd9ade182008-06-18 11:27:50 +000062 */
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +000063 FbsInputStream(InputStream in) throws IOException {
Constantin Kaplinsky8d9c34a2008-06-19 12:02:57 +000064 this(in, 0, null, 0);
Constantin Kaplinsky903009e2002-05-20 10:55:47 +000065
66 byte[] b = new byte[12];
67 readFully(b);
68
69 if (b[0] != 'F' || b[1] != 'B' || b[2] != 'S' || b[3] != ' ' ||
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +000070 b[4] != '0' || b[5] != '0' || b[6] != '1' || b[7] != '.' ||
71 b[8] < '0' || b[8] > '9' || b[9] < '0' || b[9] > '9' ||
72 b[10] < '0' || b[10] > '9' || b[11] != '\n') {
Constantin Kaplinsky8d9c34a2008-06-19 12:02:57 +000073 throw new IOException("Incorrect FBS file signature");
Constantin Kaplinsky903009e2002-05-20 10:55:47 +000074 }
Constantin Kaplinsky8d9c34a2008-06-19 12:02:57 +000075 }
Constantin Kaplinsky903009e2002-05-20 10:55:47 +000076
Constantin Kaplinsky8d9c34a2008-06-19 12:02:57 +000077 /**
78 * Construct FbsInputStream object based on the given byte array and
79 * continued in the specified InputStream. Arbitrary position in the FBS file
80 * is allowed.
81 *
82 * @param in
83 * the input stream for reading future data, after <code>buffer</code>
84 * will be exhausted. The stream should be positioned at any data block
85 * boundary (byte counter should follow in next four bytes).
86 * @param timeOffset
87 * time position corresponding the the data block provided in
88 * <code>buffer</code>.
89 * @param buffer
90 * the data block that will be treated as the beginning of this FBS data
91 * stream. This byte array is not copied into the new object so it should
92 * not be altered by the caller afterwards.
93 * @param nextBlockOffset
94 * the number of bytes that should be skipped in first data block read
95 * from <code>in</code>.
96 */
97 FbsInputStream(InputStream in, long timeOffset, byte[] buffer,
Constantin Kaplinskyd3a2df12008-06-20 20:12:22 +000098 long nextBlockOffset) {
Constantin Kaplinsky8d9c34a2008-06-19 12:02:57 +000099
100 this.in = in;
101 startTime = System.currentTimeMillis() - timeOffset;
102 this.timeOffset = timeOffset;
103 seekOffset = -1;
Constantin Kaplinsky2c0d2d12008-06-23 13:34:36 +0000104 farSeeking = false;
Constantin Kaplinsky8d9c34a2008-06-19 12:02:57 +0000105 paused = false;
106 playbackSpeed = 1.0;
107
108 this.buffer = buffer;
109 bufferSize = (buffer != null) ? buffer.length : 0;
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000110 bufferPos = 0;
Constantin Kaplinsky8d9c34a2008-06-19 12:02:57 +0000111
112 this.nextBlockOffset = nextBlockOffset;
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000113 }
114
wimba.comd1f56df2004-11-01 16:18:54 +0000115 // Force stream to finish any wait.
116 public void quit() {
117 isQuitting = true;
118 synchronized(this) {
119 notify();
120 }
121 }
122
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000123 //
124 // Basic methods overriding InputStream's methods.
125 //
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000126 public int read() throws IOException {
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000127 while (bufferSize == 0) {
128 if (!fillBuffer())
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000129 return -1;
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000130 }
131 bufferSize--;
132 return buffer[bufferPos++] & 0xFF;
133 }
134
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000135 public int available() throws IOException {
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000136 // FIXME: This will work incorrectly if our caller will wait until
137 // some amount of data is available when the buffer contains less
138 // data than then that. Current implementation never reads more
139 // data until the buffer is fully exhausted.
140 return bufferSize;
141 }
142
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000143 public synchronized void close() throws IOException {
wimba.com275f6072005-01-11 19:02:12 +0000144 if (in != null)
145 in.close();
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000146 in = null;
Constantin Kaplinskyac6420d2002-05-29 17:05:39 +0000147 startTime = -1;
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000148 timeOffset = 0;
Constantin Kaplinsky52c48242002-05-30 13:26:34 +0000149 seekOffset = -1;
Constantin Kaplinsky2c0d2d12008-06-23 13:34:36 +0000150 farSeeking = false;
Constantin Kaplinskyba0c4df2002-05-30 12:23:25 +0000151 paused = false;
Constantin Kaplinskyce39d3b2002-05-30 15:54:56 +0000152 playbackSpeed = 1.0;
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000153
154 buffer = null;
155 bufferSize = 0;
156 bufferPos = 0;
Constantin Kaplinsky5a7133a2002-07-24 17:02:04 +0000157
Constantin Kaplinsky8d9c34a2008-06-19 12:02:57 +0000158 nextBlockOffset = 0;
Constantin Kaplinsky5a7133a2002-07-24 17:02:04 +0000159 obs = null;
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000160 }
161
162 //
163 // Methods providing additional functionality.
164 //
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000165 public synchronized long getTimeOffset() {
Constantin Kaplinsky5a7133a2002-07-24 17:02:04 +0000166 long off = Math.max(seekOffset, timeOffset);
167 return (long)(off * playbackSpeed);
Constantin Kaplinskyfe079832002-05-29 00:52:32 +0000168 }
169
Constantin Kaplinskye0fd2da2008-06-23 13:57:37 +0000170 public synchronized void setTimeOffset(long pos, boolean allowJump) {
Constantin Kaplinskyce39d3b2002-05-30 15:54:56 +0000171 seekOffset = (long)(pos / playbackSpeed);
Constantin Kaplinskye0fd2da2008-06-23 13:57:37 +0000172 if (allowJump) {
173 long minJumpForwardOffset = timeOffset + (long)(10000 / playbackSpeed);
174 if (seekOffset < timeOffset || seekOffset > minJumpForwardOffset) {
175 farSeeking = true;
176 }
Constantin Kaplinskyd8770f62002-08-16 16:01:37 +0000177 }
Constantin Kaplinsky52c48242002-05-30 13:26:34 +0000178 notify();
Constantin Kaplinsky30f786a2002-05-29 10:59:52 +0000179 }
180
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000181 public synchronized void setSpeed(double newSpeed) {
Constantin Kaplinsky5a7133a2002-07-24 17:02:04 +0000182 long newOffset = (long)(timeOffset * playbackSpeed / newSpeed);
183 startTime += timeOffset - newOffset;
184 timeOffset = newOffset;
Constantin Kaplinskyce39d3b2002-05-30 15:54:56 +0000185 if (isSeeking()) {
186 seekOffset = (long)(seekOffset * playbackSpeed / newSpeed);
187 }
188 playbackSpeed = newSpeed;
189 }
190
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000191 public boolean isSeeking() {
Constantin Kaplinsky52c48242002-05-30 13:26:34 +0000192 return (seekOffset >= 0);
Constantin Kaplinsky30f786a2002-05-29 10:59:52 +0000193 }
194
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000195 public long getSeekOffset() {
Constantin Kaplinsky7cc77622002-09-22 12:36:20 +0000196 return (long)(seekOffset * playbackSpeed);
Constantin Kaplinskyd8770f62002-08-16 16:01:37 +0000197 }
198
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000199 public boolean isPaused() {
Constantin Kaplinskyd8770f62002-08-16 16:01:37 +0000200 return paused;
201 }
202
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000203 public synchronized void pausePlayback() {
Constantin Kaplinskyba0c4df2002-05-30 12:23:25 +0000204 paused = true;
Constantin Kaplinskyac6420d2002-05-29 17:05:39 +0000205 notify();
Constantin Kaplinsky30f786a2002-05-29 10:59:52 +0000206 }
207
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000208 public synchronized void resumePlayback() {
Constantin Kaplinskyba0c4df2002-05-30 12:23:25 +0000209 paused = false;
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000210 startTime = System.currentTimeMillis() - timeOffset;
Constantin Kaplinskyac6420d2002-05-29 17:05:39 +0000211 notify();
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000212 }
213
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000214 public void addObserver(Observer target) {
Constantin Kaplinsky5a7133a2002-07-24 17:02:04 +0000215 obs = target;
216 }
217
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000218 //
219 // Methods for internal use.
220 //
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000221 private synchronized boolean fillBuffer() throws IOException {
Constantin Kaplinsky2c0d2d12008-06-23 13:34:36 +0000222 // The reading thread should be interrupted on far seeking.
223 if (farSeeking)
224 throw new EOFException("[JUMP]");
Constantin Kaplinskyd8770f62002-08-16 16:01:37 +0000225
226 // Just wait unless we are performing playback OR seeking.
Constantin Kaplinskyac6420d2002-05-29 17:05:39 +0000227 waitWhilePaused();
228
Constantin Kaplinsky8d9c34a2008-06-19 12:02:57 +0000229 if (!readDataBlock()) {
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000230 return false;
231 }
232
Constantin Kaplinsky52c48242002-05-30 13:26:34 +0000233 if (seekOffset >= 0) {
234 if (timeOffset >= seekOffset) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000235 startTime = System.currentTimeMillis() - seekOffset;
236 seekOffset = -1;
Constantin Kaplinsky5a7133a2002-07-24 17:02:04 +0000237 } else {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000238 return true;
Constantin Kaplinsky52c48242002-05-30 13:26:34 +0000239 }
Constantin Kaplinsky52c48242002-05-30 13:26:34 +0000240 }
241
wimba.comd1f56df2004-11-01 16:18:54 +0000242 while (!isQuitting) {
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000243 long timeDiff = startTime + timeOffset - System.currentTimeMillis();
244 if (timeDiff <= 0) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000245 break;
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000246 }
Constantin Kaplinskyce39d3b2002-05-30 15:54:56 +0000247 try {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000248 wait(timeDiff);
Constantin Kaplinskyce39d3b2002-05-30 15:54:56 +0000249 } catch (InterruptedException e) {
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000250 }
Constantin Kaplinskyac6420d2002-05-29 17:05:39 +0000251 waitWhilePaused();
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000252 }
253
254 return true;
255 }
256
Constantin Kaplinskyab0eaa22008-06-19 07:51:42 +0000257 /**
258 * Read FBS data block into the buffer.
Constantin Kaplinsky8d9c34a2008-06-19 12:02:57 +0000259 * If {@link #nextBlockOffset} is not zero, that number of bytes will be
260 * skipped in the beginning of the data block.
Constantin Kaplinskyab0eaa22008-06-19 07:51:42 +0000261 *
Constantin Kaplinskyab0eaa22008-06-19 07:51:42 +0000262 * @return true on success, false if end of file was reached.
263 * @throws java.io.IOException can be thrown while reading from the
264 * underlying input stream, or as a result of bad FBS file data.
265 */
Constantin Kaplinsky8d9c34a2008-06-19 12:02:57 +0000266 private boolean readDataBlock() throws IOException {
Constantin Kaplinskyab0eaa22008-06-19 07:51:42 +0000267 // Read byte counter, check for EOF condition.
268 long readResult = readUnsigned32();
269 if (readResult < 0) {
270 return false;
271 }
272
273 bufferSize = (int)readResult;
Constantin Kaplinsky8d9c34a2008-06-19 12:02:57 +0000274 int alignedSize = (bufferSize + 3) & 0xFFFFFFFC;
275
276 if (nextBlockOffset > 0) {
277 in.skip(nextBlockOffset);
278 bufferSize -= nextBlockOffset;
279 alignedSize -= nextBlockOffset;
280 nextBlockOffset = 0;
281 }
282
Constantin Kaplinskyab0eaa22008-06-19 07:51:42 +0000283 if (bufferSize >= 0) {
Constantin Kaplinskyab0eaa22008-06-19 07:51:42 +0000284 buffer = new byte[alignedSize];
285 readFully(buffer);
Constantin Kaplinsky8d9c34a2008-06-19 12:02:57 +0000286 bufferPos = 0;
Constantin Kaplinskyab0eaa22008-06-19 07:51:42 +0000287 timeOffset = (long)(readUnsigned32() / playbackSpeed);
288 }
289
290 if (bufferSize < 0 || timeOffset < 0 || bufferPos >= bufferSize) {
291 buffer = null;
292 bufferSize = 0;
293 bufferPos = 0;
294 throw new IOException("Invalid FBS file data");
295 }
296
297 return true;
298 }
299
Constantin Kaplinskyac6420d2002-05-29 17:05:39 +0000300 //
301 // In paused mode, wait for external notification on this object.
302 //
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000303 private void waitWhilePaused() {
wimba.comd1f56df2004-11-01 16:18:54 +0000304 while (paused && !isSeeking() && !isQuitting) {
Constantin Kaplinskyac6420d2002-05-29 17:05:39 +0000305 synchronized(this) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000306 try {
307 // Note: we call Observer.update(Observable,Object) method
308 // directly instead of maintaining an Observable object.
309 obs.update(null, null);
310 wait();
311 } catch (InterruptedException e) {
312 }
Constantin Kaplinskyac6420d2002-05-29 17:05:39 +0000313 }
314 }
315 }
316
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000317 private long readUnsigned32() throws IOException {
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000318 byte[] buf = new byte[4];
319 if (!readFully(buf))
320 return -1;
321
322 return ((long)(buf[0] & 0xFF) << 24 |
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000323 (buf[1] & 0xFF) << 16 |
324 (buf[2] & 0xFF) << 8 |
325 (buf[3] & 0xFF));
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000326 }
327
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000328 private boolean readFully(byte[] b) throws IOException {
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000329 int off = 0;
330 int len = b.length;
331
332 while (off != len) {
333 int count = in.read(b, off, len - off);
334 if (count < 0) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000335 return false;
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000336 }
337 off += count;
338 }
339
340 return true;
341 }
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000342
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000343}
344