blob: 2e90bc4844488dae3108886850485ce335fe2d77 [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
24import java.io.*;
Constantin Kaplinsky5a7133a2002-07-24 17:02:04 +000025import java.util.*;
Constantin Kaplinsky903009e2002-05-20 10:55:47 +000026
27class FbsInputStream extends InputStream {
28
29 protected InputStream in;
30 protected long startTime;
31 protected long timeOffset;
Constantin Kaplinsky52c48242002-05-30 13:26:34 +000032 protected long seekOffset;
Constantin Kaplinskyd8770f62002-08-16 16:01:37 +000033 protected boolean seekBackwards;
Constantin Kaplinskyba0c4df2002-05-30 12:23:25 +000034 protected boolean paused;
Constantin Kaplinskyce39d3b2002-05-30 15:54:56 +000035 protected double playbackSpeed;
Constantin Kaplinsky903009e2002-05-20 10:55:47 +000036
37 protected byte[] buffer;
38 protected int bufferSize;
39 protected int bufferPos;
40
Constantin Kaplinsky5a7133a2002-07-24 17:02:04 +000041 protected Observer obs;
42
Constantin Kaplinsky903009e2002-05-20 10:55:47 +000043 //
44 // Constructors.
45 //
46
47 FbsInputStream() throws IOException {
48 throw new IOException("FbsInputStream: no such constructor");
49 }
50
Constantin Kaplinskyac6420d2002-05-29 17:05:39 +000051 //
52 // Construct FbsInputStream object, begin playback.
53 //
54
Constantin Kaplinsky903009e2002-05-20 10:55:47 +000055 FbsInputStream(InputStream in) throws IOException
56 {
57 this.in = in;
58 startTime = System.currentTimeMillis();
59 timeOffset = 0;
Constantin Kaplinsky52c48242002-05-30 13:26:34 +000060 seekOffset = -1;
Constantin Kaplinskyd8770f62002-08-16 16:01:37 +000061 seekBackwards = false;
Constantin Kaplinskyba0c4df2002-05-30 12:23:25 +000062 paused = false;
Constantin Kaplinskyce39d3b2002-05-30 15:54:56 +000063 playbackSpeed = 1.0;
Constantin Kaplinsky903009e2002-05-20 10:55:47 +000064
65 byte[] b = new byte[12];
66 readFully(b);
67
68 if (b[0] != 'F' || b[1] != 'B' || b[2] != 'S' || b[3] != ' ' ||
69 b[4] != '0' || b[5] != '0' || b[6] != '1' || b[7] != '.' ||
70 b[8] < '0' || b[8] > '9' || b[9] < '0' || b[9] > '9' ||
71 b[10] < '0' || b[10] > '9' || b[11] != '\n') {
72 throw new IOException("Incorrect protocol version");
73 }
74
75 buffer = null;
76 bufferSize = 0;
77 bufferPos = 0;
78 }
79
80 //
81 // Basic methods overriding InputStream's methods.
82 //
83
84 public int read() throws IOException
85 {
86 while (bufferSize == 0) {
87 if (!fillBuffer())
88 return -1;
89 }
90 bufferSize--;
91 return buffer[bufferPos++] & 0xFF;
92 }
93
94 public int available() throws IOException
95 {
96 // FIXME: This will work incorrectly if our caller will wait until
97 // some amount of data is available when the buffer contains less
98 // data than then that. Current implementation never reads more
99 // data until the buffer is fully exhausted.
100 return bufferSize;
101 }
102
Constantin Kaplinskyac6420d2002-05-29 17:05:39 +0000103 public synchronized void close() throws IOException
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000104 {
105 in.close();
106 in = null;
Constantin Kaplinskyac6420d2002-05-29 17:05:39 +0000107 startTime = -1;
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000108 timeOffset = 0;
Constantin Kaplinsky52c48242002-05-30 13:26:34 +0000109 seekOffset = -1;
Constantin Kaplinskyd8770f62002-08-16 16:01:37 +0000110 seekBackwards = false;
Constantin Kaplinskyba0c4df2002-05-30 12:23:25 +0000111 paused = false;
Constantin Kaplinskyce39d3b2002-05-30 15:54:56 +0000112 playbackSpeed = 1.0;
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000113
114 buffer = null;
115 bufferSize = 0;
116 bufferPos = 0;
Constantin Kaplinsky5a7133a2002-07-24 17:02:04 +0000117
118 obs = null;
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000119 }
120
121 //
122 // Methods providing additional functionality.
123 //
124
Constantin Kaplinskyce39d3b2002-05-30 15:54:56 +0000125 public synchronized long getTimeOffset()
Constantin Kaplinskyfe079832002-05-29 00:52:32 +0000126 {
Constantin Kaplinsky5a7133a2002-07-24 17:02:04 +0000127 long off = Math.max(seekOffset, timeOffset);
128 return (long)(off * playbackSpeed);
Constantin Kaplinskyfe079832002-05-29 00:52:32 +0000129 }
130
Constantin Kaplinsky972c2572002-05-30 14:19:02 +0000131 public synchronized void setTimeOffset(long pos)
Constantin Kaplinsky30f786a2002-05-29 10:59:52 +0000132 {
Constantin Kaplinskyce39d3b2002-05-30 15:54:56 +0000133 seekOffset = (long)(pos / playbackSpeed);
Constantin Kaplinskyd8770f62002-08-16 16:01:37 +0000134 if (seekOffset < timeOffset) {
135 seekBackwards = true;
136 }
Constantin Kaplinsky52c48242002-05-30 13:26:34 +0000137 notify();
Constantin Kaplinsky30f786a2002-05-29 10:59:52 +0000138 }
139
Constantin Kaplinskyce39d3b2002-05-30 15:54:56 +0000140 public synchronized void setSpeed(double newSpeed)
141 {
Constantin Kaplinsky5a7133a2002-07-24 17:02:04 +0000142 long newOffset = (long)(timeOffset * playbackSpeed / newSpeed);
143 startTime += timeOffset - newOffset;
144 timeOffset = newOffset;
Constantin Kaplinskyce39d3b2002-05-30 15:54:56 +0000145 if (isSeeking()) {
146 seekOffset = (long)(seekOffset * playbackSpeed / newSpeed);
147 }
148 playbackSpeed = newSpeed;
149 }
150
Constantin Kaplinsky30f786a2002-05-29 10:59:52 +0000151 public boolean isSeeking()
152 {
Constantin Kaplinsky52c48242002-05-30 13:26:34 +0000153 return (seekOffset >= 0);
Constantin Kaplinsky30f786a2002-05-29 10:59:52 +0000154 }
155
Constantin Kaplinskyd8770f62002-08-16 16:01:37 +0000156 public long getSeekOffset()
157 {
158 return seekOffset;
159 }
160
161 public boolean isPaused()
162 {
163 return paused;
164 }
165
Constantin Kaplinskyac6420d2002-05-29 17:05:39 +0000166 public synchronized void pausePlayback()
Constantin Kaplinsky30f786a2002-05-29 10:59:52 +0000167 {
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 Kaplinskyac6420d2002-05-29 17:05:39 +0000172 public synchronized void resumePlayback()
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000173 {
Constantin Kaplinskyba0c4df2002-05-30 12:23:25 +0000174 paused = false;
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000175 startTime = System.currentTimeMillis() - timeOffset;
Constantin Kaplinskyac6420d2002-05-29 17:05:39 +0000176 notify();
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000177 }
178
Constantin Kaplinsky5a7133a2002-07-24 17:02:04 +0000179 public void addObserver(Observer target)
180 {
181 obs = target;
182 }
183
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000184 //
185 // Methods for internal use.
186 //
187
Constantin Kaplinskyce39d3b2002-05-30 15:54:56 +0000188 private synchronized boolean fillBuffer() throws IOException
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000189 {
Constantin Kaplinskyd8770f62002-08-16 16:01:37 +0000190 // The reading thread should be interrupted on backward seeking.
191 if (seekBackwards)
192 throw new EOFException("[REWIND]");
193
194 // Just wait unless we are performing playback OR seeking.
Constantin Kaplinskyac6420d2002-05-29 17:05:39 +0000195 waitWhilePaused();
196
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000197 bufferSize = (int)readUnsigned32();
198 if (bufferSize >= 0) {
199 int realSize = (bufferSize + 3) & 0xFFFFFFFC;
200 buffer = new byte[realSize];
201 readFully(buffer);
202 bufferPos = 0;
Constantin Kaplinskyce39d3b2002-05-30 15:54:56 +0000203 timeOffset = (long)(readUnsigned32() / playbackSpeed);
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000204 }
205
206 if (bufferSize < 0 || timeOffset < 0) {
207 buffer = null;
208 bufferSize = 0;
209 bufferPos = 0;
210 return false;
211 }
212
Constantin Kaplinsky52c48242002-05-30 13:26:34 +0000213 if (seekOffset >= 0) {
214 if (timeOffset >= seekOffset) {
Constantin Kaplinsky5a7133a2002-07-24 17:02:04 +0000215 startTime = System.currentTimeMillis() - seekOffset;
Constantin Kaplinsky52c48242002-05-30 13:26:34 +0000216 seekOffset = -1;
Constantin Kaplinsky5a7133a2002-07-24 17:02:04 +0000217 } else {
218 return true;
Constantin Kaplinsky52c48242002-05-30 13:26:34 +0000219 }
Constantin Kaplinsky52c48242002-05-30 13:26:34 +0000220 }
221
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000222 while (true) {
223 long timeDiff = startTime + timeOffset - System.currentTimeMillis();
224 if (timeDiff <= 0) {
225 break;
226 }
Constantin Kaplinskyce39d3b2002-05-30 15:54:56 +0000227 try {
228 wait(timeDiff);
229 } catch (InterruptedException e) {
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000230 }
Constantin Kaplinskyac6420d2002-05-29 17:05:39 +0000231 waitWhilePaused();
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000232 }
233
234 return true;
235 }
236
Constantin Kaplinskyac6420d2002-05-29 17:05:39 +0000237 //
238 // In paused mode, wait for external notification on this object.
239 //
240
241 private void waitWhilePaused()
242 {
Constantin Kaplinsky52c48242002-05-30 13:26:34 +0000243 while (paused && !isSeeking()) {
Constantin Kaplinskyac6420d2002-05-29 17:05:39 +0000244 synchronized(this) {
245 try {
Constantin Kaplinsky5a7133a2002-07-24 17:02:04 +0000246 // Note: we call Observer.update(Observable,Object) method
247 // directly instead of maintaining an Observable object.
248 obs.update(null, null);
Constantin Kaplinskyac6420d2002-05-29 17:05:39 +0000249 wait();
250 } catch (InterruptedException e) {
251 }
252 }
253 }
254 }
255
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000256 private long readUnsigned32() throws IOException
257 {
258 byte[] buf = new byte[4];
259 if (!readFully(buf))
260 return -1;
261
262 return ((long)(buf[0] & 0xFF) << 24 |
263 (buf[1] & 0xFF) << 16 |
264 (buf[2] & 0xFF) << 8 |
265 (buf[3] & 0xFF));
266 }
267
268 private boolean readFully(byte[] b) throws IOException
269 {
270 int off = 0;
271 int len = b.length;
272
273 while (off != len) {
274 int count = in.read(b, off, len - off);
275 if (count < 0) {
276 return false;
277 }
278 off += count;
279 }
280
281 return true;
282 }
283}
284