blob: 2077bc5aae86c7eba274d5ac7bc2eeab37fe03e4 [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 Kaplinsky72e47ef2008-04-18 17:48:16 +0000170 public synchronized void setTimeOffset(long pos) {
Constantin Kaplinskyce39d3b2002-05-30 15:54:56 +0000171 seekOffset = (long)(pos / playbackSpeed);
Constantin Kaplinsky2c0d2d12008-06-23 13:34:36 +0000172 long minJumpForwardOffset = timeOffset + (long)(10000 / playbackSpeed);
173 if (seekOffset < timeOffset || seekOffset > minJumpForwardOffset) {
174 farSeeking = true;
Constantin Kaplinskyd8770f62002-08-16 16:01:37 +0000175 }
Constantin Kaplinsky52c48242002-05-30 13:26:34 +0000176 notify();
Constantin Kaplinsky30f786a2002-05-29 10:59:52 +0000177 }
178
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000179 public synchronized void setSpeed(double newSpeed) {
Constantin Kaplinsky5a7133a2002-07-24 17:02:04 +0000180 long newOffset = (long)(timeOffset * playbackSpeed / newSpeed);
181 startTime += timeOffset - newOffset;
182 timeOffset = newOffset;
Constantin Kaplinskyce39d3b2002-05-30 15:54:56 +0000183 if (isSeeking()) {
184 seekOffset = (long)(seekOffset * playbackSpeed / newSpeed);
185 }
186 playbackSpeed = newSpeed;
187 }
188
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000189 public boolean isSeeking() {
Constantin Kaplinsky52c48242002-05-30 13:26:34 +0000190 return (seekOffset >= 0);
Constantin Kaplinsky30f786a2002-05-29 10:59:52 +0000191 }
192
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000193 public long getSeekOffset() {
Constantin Kaplinsky7cc77622002-09-22 12:36:20 +0000194 return (long)(seekOffset * playbackSpeed);
Constantin Kaplinskyd8770f62002-08-16 16:01:37 +0000195 }
196
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000197 public boolean isPaused() {
Constantin Kaplinskyd8770f62002-08-16 16:01:37 +0000198 return paused;
199 }
200
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000201 public synchronized void pausePlayback() {
Constantin Kaplinskyba0c4df2002-05-30 12:23:25 +0000202 paused = true;
Constantin Kaplinskyac6420d2002-05-29 17:05:39 +0000203 notify();
Constantin Kaplinsky30f786a2002-05-29 10:59:52 +0000204 }
205
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000206 public synchronized void resumePlayback() {
Constantin Kaplinskyba0c4df2002-05-30 12:23:25 +0000207 paused = false;
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000208 startTime = System.currentTimeMillis() - timeOffset;
Constantin Kaplinskyac6420d2002-05-29 17:05:39 +0000209 notify();
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000210 }
211
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000212 public void addObserver(Observer target) {
Constantin Kaplinsky5a7133a2002-07-24 17:02:04 +0000213 obs = target;
214 }
215
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000216 //
217 // Methods for internal use.
218 //
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000219 private synchronized boolean fillBuffer() throws IOException {
Constantin Kaplinsky2c0d2d12008-06-23 13:34:36 +0000220 // The reading thread should be interrupted on far seeking.
221 if (farSeeking)
222 throw new EOFException("[JUMP]");
Constantin Kaplinskyd8770f62002-08-16 16:01:37 +0000223
224 // Just wait unless we are performing playback OR seeking.
Constantin Kaplinskyac6420d2002-05-29 17:05:39 +0000225 waitWhilePaused();
226
Constantin Kaplinsky8d9c34a2008-06-19 12:02:57 +0000227 if (!readDataBlock()) {
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000228 return false;
229 }
230
Constantin Kaplinsky52c48242002-05-30 13:26:34 +0000231 if (seekOffset >= 0) {
232 if (timeOffset >= seekOffset) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000233 startTime = System.currentTimeMillis() - seekOffset;
234 seekOffset = -1;
Constantin Kaplinsky5a7133a2002-07-24 17:02:04 +0000235 } else {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000236 return true;
Constantin Kaplinsky52c48242002-05-30 13:26:34 +0000237 }
Constantin Kaplinsky52c48242002-05-30 13:26:34 +0000238 }
239
wimba.comd1f56df2004-11-01 16:18:54 +0000240 while (!isQuitting) {
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000241 long timeDiff = startTime + timeOffset - System.currentTimeMillis();
242 if (timeDiff <= 0) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000243 break;
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000244 }
Constantin Kaplinskyce39d3b2002-05-30 15:54:56 +0000245 try {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000246 wait(timeDiff);
Constantin Kaplinskyce39d3b2002-05-30 15:54:56 +0000247 } catch (InterruptedException e) {
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000248 }
Constantin Kaplinskyac6420d2002-05-29 17:05:39 +0000249 waitWhilePaused();
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000250 }
251
252 return true;
253 }
254
Constantin Kaplinskyab0eaa22008-06-19 07:51:42 +0000255 /**
256 * Read FBS data block into the buffer.
Constantin Kaplinsky8d9c34a2008-06-19 12:02:57 +0000257 * If {@link #nextBlockOffset} is not zero, that number of bytes will be
258 * skipped in the beginning of the data block.
Constantin Kaplinskyab0eaa22008-06-19 07:51:42 +0000259 *
Constantin Kaplinskyab0eaa22008-06-19 07:51:42 +0000260 * @return true on success, false if end of file was reached.
261 * @throws java.io.IOException can be thrown while reading from the
262 * underlying input stream, or as a result of bad FBS file data.
263 */
Constantin Kaplinsky8d9c34a2008-06-19 12:02:57 +0000264 private boolean readDataBlock() throws IOException {
Constantin Kaplinskyab0eaa22008-06-19 07:51:42 +0000265 // Read byte counter, check for EOF condition.
266 long readResult = readUnsigned32();
267 if (readResult < 0) {
268 return false;
269 }
270
271 bufferSize = (int)readResult;
Constantin Kaplinsky8d9c34a2008-06-19 12:02:57 +0000272 int alignedSize = (bufferSize + 3) & 0xFFFFFFFC;
273
274 if (nextBlockOffset > 0) {
275 in.skip(nextBlockOffset);
276 bufferSize -= nextBlockOffset;
277 alignedSize -= nextBlockOffset;
278 nextBlockOffset = 0;
279 }
280
Constantin Kaplinskyab0eaa22008-06-19 07:51:42 +0000281 if (bufferSize >= 0) {
Constantin Kaplinskyab0eaa22008-06-19 07:51:42 +0000282 buffer = new byte[alignedSize];
283 readFully(buffer);
Constantin Kaplinsky8d9c34a2008-06-19 12:02:57 +0000284 bufferPos = 0;
Constantin Kaplinskyab0eaa22008-06-19 07:51:42 +0000285 timeOffset = (long)(readUnsigned32() / playbackSpeed);
286 }
287
288 if (bufferSize < 0 || timeOffset < 0 || bufferPos >= bufferSize) {
289 buffer = null;
290 bufferSize = 0;
291 bufferPos = 0;
292 throw new IOException("Invalid FBS file data");
293 }
294
295 return true;
296 }
297
Constantin Kaplinskyac6420d2002-05-29 17:05:39 +0000298 //
299 // In paused mode, wait for external notification on this object.
300 //
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000301 private void waitWhilePaused() {
wimba.comd1f56df2004-11-01 16:18:54 +0000302 while (paused && !isSeeking() && !isQuitting) {
Constantin Kaplinskyac6420d2002-05-29 17:05:39 +0000303 synchronized(this) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000304 try {
305 // Note: we call Observer.update(Observable,Object) method
306 // directly instead of maintaining an Observable object.
307 obs.update(null, null);
308 wait();
309 } catch (InterruptedException e) {
310 }
Constantin Kaplinskyac6420d2002-05-29 17:05:39 +0000311 }
312 }
313 }
314
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000315 private long readUnsigned32() throws IOException {
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000316 byte[] buf = new byte[4];
317 if (!readFully(buf))
318 return -1;
319
320 return ((long)(buf[0] & 0xFF) << 24 |
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000321 (buf[1] & 0xFF) << 16 |
322 (buf[2] & 0xFF) << 8 |
323 (buf[3] & 0xFF));
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000324 }
325
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000326 private boolean readFully(byte[] b) throws IOException {
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000327 int off = 0;
328 int len = b.length;
329
330 while (off != len) {
331 int count = in.read(b, off, len - off);
332 if (count < 0) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000333 return false;
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000334 }
335 off += count;
336 }
337
338 return true;
339 }
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000340
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000341}
342