blob: e12c9f5a167fb56709e2fdcb364bd05e2b271466 [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
Constantin Kaplinskycf689b32008-04-30 12:50:34 +000024package com.tightvnc.rfbplayer;
wimba.comc23aeb02004-09-16 00:00:00 +000025
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;
wimba.comd1f56df2004-11-01 16:18:54 +000037 protected boolean isQuitting = false;
Constantin Kaplinskyce39d3b2002-05-30 15:54:56 +000038 protected double playbackSpeed;
Constantin Kaplinsky903009e2002-05-20 10:55:47 +000039
40 protected byte[] buffer;
41 protected int bufferSize;
42 protected int bufferPos;
43
Constantin Kaplinsky5a7133a2002-07-24 17:02:04 +000044 protected Observer obs;
45
Constantin Kaplinskyd9ade182008-06-18 11:27:50 +000046 /**
Constantin Kaplinsky7fea1c42008-06-19 03:31:40 +000047 * Construct FbsInputStream object based on the given InputStream, positioned
48 * at the very beginning of the corresponding FBS file. This constructor
49 * reads and checks FBS file signature which would look like "FBS 001.000\n",
50 * but any decimal number is allowed after the dot.
Constantin Kaplinskyd9ade182008-06-18 11:27:50 +000051 *
Constantin Kaplinsky7fea1c42008-06-19 03:31:40 +000052 * @param in the InputStream object that will be used as a base for this new
53 * FbsInputStream instance. It should be positioned at the very beginning of
54 * the corresponding FBS file, so that first 12 bytes read from the stream
55 * should form FBS file signature.
56 * @throws java.io.IOException thrown on read error or on incorrect FBS file
57 * signature.
Constantin Kaplinskyd9ade182008-06-18 11:27:50 +000058 */
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +000059 FbsInputStream(InputStream in) throws IOException {
Constantin Kaplinsky903009e2002-05-20 10:55:47 +000060 this.in = in;
61 startTime = System.currentTimeMillis();
62 timeOffset = 0;
Constantin Kaplinsky52c48242002-05-30 13:26:34 +000063 seekOffset = -1;
Constantin Kaplinskyd8770f62002-08-16 16:01:37 +000064 seekBackwards = false;
Constantin Kaplinskyba0c4df2002-05-30 12:23:25 +000065 paused = false;
Constantin Kaplinskyce39d3b2002-05-30 15:54:56 +000066 playbackSpeed = 1.0;
Constantin Kaplinsky903009e2002-05-20 10:55:47 +000067
68 byte[] b = new byte[12];
69 readFully(b);
70
71 if (b[0] != 'F' || b[1] != 'B' || b[2] != 'S' || b[3] != ' ' ||
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +000072 b[4] != '0' || b[5] != '0' || b[6] != '1' || b[7] != '.' ||
73 b[8] < '0' || b[8] > '9' || b[9] < '0' || b[9] > '9' ||
74 b[10] < '0' || b[10] > '9' || b[11] != '\n') {
Constantin Kaplinsky903009e2002-05-20 10:55:47 +000075 throw new IOException("Incorrect protocol version");
76 }
77
78 buffer = null;
79 bufferSize = 0;
80 bufferPos = 0;
81 }
82
wimba.comd1f56df2004-11-01 16:18:54 +000083 // Force stream to finish any wait.
84 public void quit() {
85 isQuitting = true;
86 synchronized(this) {
87 notify();
88 }
89 }
90
Constantin Kaplinsky903009e2002-05-20 10:55:47 +000091 //
92 // Basic methods overriding InputStream's methods.
93 //
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +000094 public int read() throws IOException {
Constantin Kaplinsky903009e2002-05-20 10:55:47 +000095 while (bufferSize == 0) {
96 if (!fillBuffer())
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +000097 return -1;
Constantin Kaplinsky903009e2002-05-20 10:55:47 +000098 }
99 bufferSize--;
100 return buffer[bufferPos++] & 0xFF;
101 }
102
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000103 public int available() throws IOException {
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000104 // FIXME: This will work incorrectly if our caller will wait until
105 // some amount of data is available when the buffer contains less
106 // data than then that. Current implementation never reads more
107 // data until the buffer is fully exhausted.
108 return bufferSize;
109 }
110
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000111 public synchronized void close() throws IOException {
wimba.com275f6072005-01-11 19:02:12 +0000112 if (in != null)
113 in.close();
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000114 in = null;
Constantin Kaplinskyac6420d2002-05-29 17:05:39 +0000115 startTime = -1;
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000116 timeOffset = 0;
Constantin Kaplinsky52c48242002-05-30 13:26:34 +0000117 seekOffset = -1;
Constantin Kaplinskyd8770f62002-08-16 16:01:37 +0000118 seekBackwards = false;
Constantin Kaplinskyba0c4df2002-05-30 12:23:25 +0000119 paused = false;
Constantin Kaplinskyce39d3b2002-05-30 15:54:56 +0000120 playbackSpeed = 1.0;
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000121
122 buffer = null;
123 bufferSize = 0;
124 bufferPos = 0;
Constantin Kaplinsky5a7133a2002-07-24 17:02:04 +0000125
126 obs = null;
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000127 }
128
129 //
130 // Methods providing additional functionality.
131 //
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000132 public synchronized long getTimeOffset() {
Constantin Kaplinsky5a7133a2002-07-24 17:02:04 +0000133 long off = Math.max(seekOffset, timeOffset);
134 return (long)(off * playbackSpeed);
Constantin Kaplinskyfe079832002-05-29 00:52:32 +0000135 }
136
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000137 public synchronized void setTimeOffset(long pos) {
Constantin Kaplinskyce39d3b2002-05-30 15:54:56 +0000138 seekOffset = (long)(pos / playbackSpeed);
Constantin Kaplinskyd8770f62002-08-16 16:01:37 +0000139 if (seekOffset < timeOffset) {
140 seekBackwards = true;
141 }
Constantin Kaplinsky52c48242002-05-30 13:26:34 +0000142 notify();
Constantin Kaplinsky30f786a2002-05-29 10:59:52 +0000143 }
144
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000145 public synchronized void setSpeed(double newSpeed) {
Constantin Kaplinsky5a7133a2002-07-24 17:02:04 +0000146 long newOffset = (long)(timeOffset * playbackSpeed / newSpeed);
147 startTime += timeOffset - newOffset;
148 timeOffset = newOffset;
Constantin Kaplinskyce39d3b2002-05-30 15:54:56 +0000149 if (isSeeking()) {
150 seekOffset = (long)(seekOffset * playbackSpeed / newSpeed);
151 }
152 playbackSpeed = newSpeed;
153 }
154
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000155 public boolean isSeeking() {
Constantin Kaplinsky52c48242002-05-30 13:26:34 +0000156 return (seekOffset >= 0);
Constantin Kaplinsky30f786a2002-05-29 10:59:52 +0000157 }
158
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000159 public long getSeekOffset() {
Constantin Kaplinsky7cc77622002-09-22 12:36:20 +0000160 return (long)(seekOffset * playbackSpeed);
Constantin Kaplinskyd8770f62002-08-16 16:01:37 +0000161 }
162
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000163 public boolean isPaused() {
Constantin Kaplinskyd8770f62002-08-16 16:01:37 +0000164 return paused;
165 }
166
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000167 public synchronized void pausePlayback() {
Constantin Kaplinskyba0c4df2002-05-30 12:23:25 +0000168 paused = true;
Constantin Kaplinskyac6420d2002-05-29 17:05:39 +0000169 notify();
Constantin Kaplinsky30f786a2002-05-29 10:59:52 +0000170 }
171
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000172 public synchronized void resumePlayback() {
Constantin Kaplinskyba0c4df2002-05-30 12:23:25 +0000173 paused = false;
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000174 startTime = System.currentTimeMillis() - timeOffset;
Constantin Kaplinskyac6420d2002-05-29 17:05:39 +0000175 notify();
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000176 }
177
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000178 public void addObserver(Observer target) {
Constantin Kaplinsky5a7133a2002-07-24 17:02:04 +0000179 obs = target;
180 }
181
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000182 //
183 // Methods for internal use.
184 //
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000185 private synchronized boolean fillBuffer() throws IOException {
Constantin Kaplinskyd8770f62002-08-16 16:01:37 +0000186 // The reading thread should be interrupted on backward seeking.
187 if (seekBackwards)
188 throw new EOFException("[REWIND]");
189
190 // Just wait unless we are performing playback OR seeking.
Constantin Kaplinskyac6420d2002-05-29 17:05:39 +0000191 waitWhilePaused();
192
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000193 bufferSize = (int)readUnsigned32();
194 if (bufferSize >= 0) {
195 int realSize = (bufferSize + 3) & 0xFFFFFFFC;
196 buffer = new byte[realSize];
197 readFully(buffer);
198 bufferPos = 0;
Constantin Kaplinskyce39d3b2002-05-30 15:54:56 +0000199 timeOffset = (long)(readUnsigned32() / playbackSpeed);
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000200 }
201
202 if (bufferSize < 0 || timeOffset < 0) {
203 buffer = null;
204 bufferSize = 0;
205 bufferPos = 0;
206 return false;
207 }
208
Constantin Kaplinsky52c48242002-05-30 13:26:34 +0000209 if (seekOffset >= 0) {
210 if (timeOffset >= seekOffset) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000211 startTime = System.currentTimeMillis() - seekOffset;
212 seekOffset = -1;
Constantin Kaplinsky5a7133a2002-07-24 17:02:04 +0000213 } else {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000214 return true;
Constantin Kaplinsky52c48242002-05-30 13:26:34 +0000215 }
Constantin Kaplinsky52c48242002-05-30 13:26:34 +0000216 }
217
wimba.comd1f56df2004-11-01 16:18:54 +0000218 while (!isQuitting) {
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000219 long timeDiff = startTime + timeOffset - System.currentTimeMillis();
220 if (timeDiff <= 0) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000221 break;
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000222 }
Constantin Kaplinskyce39d3b2002-05-30 15:54:56 +0000223 try {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000224 wait(timeDiff);
Constantin Kaplinskyce39d3b2002-05-30 15:54:56 +0000225 } catch (InterruptedException e) {
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000226 }
Constantin Kaplinskyac6420d2002-05-29 17:05:39 +0000227 waitWhilePaused();
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000228 }
229
230 return true;
231 }
232
Constantin Kaplinskyac6420d2002-05-29 17:05:39 +0000233 //
234 // In paused mode, wait for external notification on this object.
235 //
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000236 private void waitWhilePaused() {
wimba.comd1f56df2004-11-01 16:18:54 +0000237 while (paused && !isSeeking() && !isQuitting) {
Constantin Kaplinskyac6420d2002-05-29 17:05:39 +0000238 synchronized(this) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000239 try {
240 // Note: we call Observer.update(Observable,Object) method
241 // directly instead of maintaining an Observable object.
242 obs.update(null, null);
243 wait();
244 } catch (InterruptedException e) {
245 }
Constantin Kaplinskyac6420d2002-05-29 17:05:39 +0000246 }
247 }
248 }
249
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000250 private long readUnsigned32() throws IOException {
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000251 byte[] buf = new byte[4];
252 if (!readFully(buf))
253 return -1;
254
255 return ((long)(buf[0] & 0xFF) << 24 |
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000256 (buf[1] & 0xFF) << 16 |
257 (buf[2] & 0xFF) << 8 |
258 (buf[3] & 0xFF));
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000259 }
260
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000261 private boolean readFully(byte[] b) throws IOException {
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000262 int off = 0;
263 int len = b.length;
264
265 while (off != len) {
266 int count = in.read(b, off, len - off);
267 if (count < 0) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000268 return false;
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000269 }
270 off += count;
271 }
272
273 return true;
274 }
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000275
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000276}
277