blob: 68bf8de208a7eee75d098b6aa7255bb97eececa6 [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 Kaplinskyd8770f62002-08-16 16:01:37 +000036 protected boolean seekBackwards;
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. */
46 protected int nextBlockOffset;
47
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,
98 int nextBlockOffset) {
99
100 this.in = in;
101 startTime = System.currentTimeMillis() - timeOffset;
102 this.timeOffset = timeOffset;
103 seekOffset = -1;
104 seekBackwards = false;
105 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 Kaplinskyd8770f62002-08-16 16:01:37 +0000150 seekBackwards = 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 Kaplinskyd8770f62002-08-16 16:01:37 +0000172 if (seekOffset < timeOffset) {
173 seekBackwards = true;
174 }
Constantin Kaplinsky52c48242002-05-30 13:26:34 +0000175 notify();
Constantin Kaplinsky30f786a2002-05-29 10:59:52 +0000176 }
177
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000178 public synchronized void setSpeed(double newSpeed) {
Constantin Kaplinsky5a7133a2002-07-24 17:02:04 +0000179 long newOffset = (long)(timeOffset * playbackSpeed / newSpeed);
180 startTime += timeOffset - newOffset;
181 timeOffset = newOffset;
Constantin Kaplinskyce39d3b2002-05-30 15:54:56 +0000182 if (isSeeking()) {
183 seekOffset = (long)(seekOffset * playbackSpeed / newSpeed);
184 }
185 playbackSpeed = newSpeed;
186 }
187
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000188 public boolean isSeeking() {
Constantin Kaplinsky52c48242002-05-30 13:26:34 +0000189 return (seekOffset >= 0);
Constantin Kaplinsky30f786a2002-05-29 10:59:52 +0000190 }
191
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000192 public long getSeekOffset() {
Constantin Kaplinsky7cc77622002-09-22 12:36:20 +0000193 return (long)(seekOffset * playbackSpeed);
Constantin Kaplinskyd8770f62002-08-16 16:01:37 +0000194 }
195
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000196 public boolean isPaused() {
Constantin Kaplinskyd8770f62002-08-16 16:01:37 +0000197 return paused;
198 }
199
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000200 public synchronized void pausePlayback() {
Constantin Kaplinskyba0c4df2002-05-30 12:23:25 +0000201 paused = true;
Constantin Kaplinskyac6420d2002-05-29 17:05:39 +0000202 notify();
Constantin Kaplinsky30f786a2002-05-29 10:59:52 +0000203 }
204
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000205 public synchronized void resumePlayback() {
Constantin Kaplinskyba0c4df2002-05-30 12:23:25 +0000206 paused = false;
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000207 startTime = System.currentTimeMillis() - timeOffset;
Constantin Kaplinskyac6420d2002-05-29 17:05:39 +0000208 notify();
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000209 }
210
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000211 public void addObserver(Observer target) {
Constantin Kaplinsky5a7133a2002-07-24 17:02:04 +0000212 obs = target;
213 }
214
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000215 //
216 // Methods for internal use.
217 //
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000218 private synchronized boolean fillBuffer() throws IOException {
Constantin Kaplinskyd8770f62002-08-16 16:01:37 +0000219 // The reading thread should be interrupted on backward seeking.
220 if (seekBackwards)
221 throw new EOFException("[REWIND]");
222
223 // Just wait unless we are performing playback OR seeking.
Constantin Kaplinskyac6420d2002-05-29 17:05:39 +0000224 waitWhilePaused();
225
Constantin Kaplinsky8d9c34a2008-06-19 12:02:57 +0000226 if (!readDataBlock()) {
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000227 return false;
228 }
229
Constantin Kaplinsky52c48242002-05-30 13:26:34 +0000230 if (seekOffset >= 0) {
231 if (timeOffset >= seekOffset) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000232 startTime = System.currentTimeMillis() - seekOffset;
233 seekOffset = -1;
Constantin Kaplinsky5a7133a2002-07-24 17:02:04 +0000234 } else {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000235 return true;
Constantin Kaplinsky52c48242002-05-30 13:26:34 +0000236 }
Constantin Kaplinsky52c48242002-05-30 13:26:34 +0000237 }
238
wimba.comd1f56df2004-11-01 16:18:54 +0000239 while (!isQuitting) {
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000240 long timeDiff = startTime + timeOffset - System.currentTimeMillis();
241 if (timeDiff <= 0) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000242 break;
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000243 }
Constantin Kaplinskyce39d3b2002-05-30 15:54:56 +0000244 try {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000245 wait(timeDiff);
Constantin Kaplinskyce39d3b2002-05-30 15:54:56 +0000246 } catch (InterruptedException e) {
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000247 }
Constantin Kaplinskyac6420d2002-05-29 17:05:39 +0000248 waitWhilePaused();
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000249 }
250
251 return true;
252 }
253
Constantin Kaplinskyab0eaa22008-06-19 07:51:42 +0000254 /**
255 * Read FBS data block into the buffer.
Constantin Kaplinsky8d9c34a2008-06-19 12:02:57 +0000256 * If {@link #nextBlockOffset} is not zero, that number of bytes will be
257 * skipped in the beginning of the data block.
Constantin Kaplinskyab0eaa22008-06-19 07:51:42 +0000258 *
Constantin Kaplinskyab0eaa22008-06-19 07:51:42 +0000259 * @return true on success, false if end of file was reached.
260 * @throws java.io.IOException can be thrown while reading from the
261 * underlying input stream, or as a result of bad FBS file data.
262 */
Constantin Kaplinsky8d9c34a2008-06-19 12:02:57 +0000263 private boolean readDataBlock() throws IOException {
Constantin Kaplinskyab0eaa22008-06-19 07:51:42 +0000264 // Read byte counter, check for EOF condition.
265 long readResult = readUnsigned32();
266 if (readResult < 0) {
267 return false;
268 }
269
270 bufferSize = (int)readResult;
Constantin Kaplinsky8d9c34a2008-06-19 12:02:57 +0000271 int alignedSize = (bufferSize + 3) & 0xFFFFFFFC;
272
273 if (nextBlockOffset > 0) {
274 in.skip(nextBlockOffset);
275 bufferSize -= nextBlockOffset;
276 alignedSize -= nextBlockOffset;
277 nextBlockOffset = 0;
278 }
279
Constantin Kaplinskyab0eaa22008-06-19 07:51:42 +0000280 if (bufferSize >= 0) {
Constantin Kaplinskyab0eaa22008-06-19 07:51:42 +0000281 buffer = new byte[alignedSize];
282 readFully(buffer);
Constantin Kaplinsky8d9c34a2008-06-19 12:02:57 +0000283 bufferPos = 0;
Constantin Kaplinskyab0eaa22008-06-19 07:51:42 +0000284 timeOffset = (long)(readUnsigned32() / playbackSpeed);
285 }
286
287 if (bufferSize < 0 || timeOffset < 0 || bufferPos >= bufferSize) {
288 buffer = null;
289 bufferSize = 0;
290 bufferPos = 0;
291 throw new IOException("Invalid FBS file data");
292 }
293
294 return true;
295 }
296
Constantin Kaplinskyac6420d2002-05-29 17:05:39 +0000297 //
298 // In paused mode, wait for external notification on this object.
299 //
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000300 private void waitWhilePaused() {
wimba.comd1f56df2004-11-01 16:18:54 +0000301 while (paused && !isSeeking() && !isQuitting) {
Constantin Kaplinskyac6420d2002-05-29 17:05:39 +0000302 synchronized(this) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000303 try {
304 // Note: we call Observer.update(Observable,Object) method
305 // directly instead of maintaining an Observable object.
306 obs.update(null, null);
307 wait();
308 } catch (InterruptedException e) {
309 }
Constantin Kaplinskyac6420d2002-05-29 17:05:39 +0000310 }
311 }
312 }
313
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000314 private long readUnsigned32() throws IOException {
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000315 byte[] buf = new byte[4];
316 if (!readFully(buf))
317 return -1;
318
319 return ((long)(buf[0] & 0xFF) << 24 |
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000320 (buf[1] & 0xFF) << 16 |
321 (buf[2] & 0xFF) << 8 |
322 (buf[3] & 0xFF));
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000323 }
324
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000325 private boolean readFully(byte[] b) throws IOException {
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000326 int off = 0;
327 int len = b.length;
328
329 while (off != len) {
330 int count = in.read(b, off, len - off);
331 if (count < 0) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000332 return false;
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000333 }
334 off += count;
335 }
336
337 return true;
338 }
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000339
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000340}
341