-
Notifications
You must be signed in to change notification settings - Fork 263
/
Copy pathHLSStreamProvider.as
497 lines (459 loc) · 18.3 KB
/
HLSStreamProvider.as
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mangui.flowplayer {
import org.mangui.hls.event.HLSEvent;
import org.mangui.hls.utils.Params2Settings;
import flash.display.DisplayObject;
import flash.net.NetConnection;
import flash.net.NetStream;
import flash.utils.Dictionary;
import flash.media.Video;
import org.mangui.hls.HLS;
import org.mangui.hls.constant.HLSPlayStates;
import org.flowplayer.model.Plugin;
import org.flowplayer.model.PluginModel;
import org.flowplayer.view.Flowplayer;
import org.flowplayer.controller.StreamProvider;
import org.flowplayer.controller.TimeProvider;
import org.flowplayer.controller.VolumeController;
import org.flowplayer.model.Clip;
import org.flowplayer.model.ClipType;
import org.flowplayer.model.ClipEvent;
import org.flowplayer.model.ClipEventType;
import org.flowplayer.model.Playlist;
import org.flowplayer.view.StageVideoWrapper;
CONFIG::LOGGING {
import org.mangui.hls.utils.Log;
}
public class HLSStreamProvider implements StreamProvider,Plugin {
private var _volumecontroller : VolumeController;
private var _playlist : Playlist;
private var _timeProvider : TimeProvider;
private var _model : PluginModel;
private var _player : Flowplayer;
private var _clip : Clip;
private var _video : Video;
/** reference to the framework. **/
private var _hls : HLS;
// event values
private var _position : Number = 0;
private var _duration : Number = 0;
private var _durationCapped : Number = 0;
private var _bufferedTime : Number = 0;
private var _videoWidth : int = -1;
private var _videoHeight : int = -1;
private var _isManifestLoaded : Boolean = false;
private var _pauseAfterStart : Boolean;
private var _seekable : Boolean = false;
public function getDefaultConfig() : Object {
return null;
}
public function onConfig(model : PluginModel) : void {
CONFIG::LOGGING {
Log.info("onConfig()");
}
_model = model;
}
public function onLoad(player : Flowplayer) : void {
CONFIG::LOGGING {
Log.info("onLoad()");
}
_player = player;
_hls = new HLS();
_hls.stage = player.screen.getDisplayObject().stage;
_hls.addEventListener(HLSEvent.PLAYBACK_COMPLETE, _completeHandler);
_hls.addEventListener(HLSEvent.ERROR, _errorHandler);
_hls.addEventListener(HLSEvent.MANIFEST_LOADED, _manifestHandler);
_hls.addEventListener(HLSEvent.MEDIA_TIME, _mediaTimeHandler);
_hls.addEventListener(HLSEvent.PLAYBACK_STATE, _stateHandler);
_hls.addEventListener(HLSEvent.ID3_UPDATED, _ID3Handler);
var cfg : Object = _model.config;
for (var object : String in cfg) {
var subidx : int = object.indexOf("hls_");
if (subidx != -1) {
Params2Settings.set(object.substr(4), cfg[object]);
}
}
_model.dispatchOnLoad();
}
private function _completeHandler(event : HLSEvent) : void {
// dispatch a before event because the finish has default behavior that can be prevented by listeners
_clip.dispatchBeforeEvent(new ClipEvent(ClipEventType.FINISH));
_clip.startDispatched = false;
};
private function _errorHandler(event : HLSEvent) : void {
};
private function _ID3Handler(event : HLSEvent) : void {
_clip.dispatch(ClipEventType.NETSTREAM_EVENT, "onID3", event.ID3Data);
};
private function _manifestHandler(event : HLSEvent) : void {
_duration = event.levels[_hls.startlevel].duration - _clip.start;
_isManifestLoaded = true;
// only update duration if not capped
if (!_durationCapped) {
_clip.duration = _duration;
} else {
// ensure capped duration is lt real one
_durationCapped = Math.min(_durationCapped, _duration);
}
_clip.stopLiveOnPause = false;
/*
var nbLevel = event.levels.length;
if (nbLevel > 1) {
var bitrates : Array = new Array();
for (var i : int = 0; i < nbLevel; i++) {
var info : Object = new Object();
var level : Level = event.levels[i];
info.bitrate = level.bitrate;
info.url = level.url;
info.width = level.width;
info.height = level.height;
info.isDefault = (i == _hls.startlevel);
bitrates.push(info);
}
_clip.setCustomProperty("bitrates", bitrates);
}
*/
_clip.dispatch(ClipEventType.METADATA);
_seekable = true;
// real seek position : add clip.start offset. if not defined, use -1 to fix seeking issue on live playlist
_hls.stream.play(null, (_clip.start == 0) ? -1 : _clip.start);
_clip.dispatch(ClipEventType.SEEK, 0);
if (_pauseAfterStart) {
pause(new ClipEvent(ClipEventType.PAUSE));
}
};
private function _mediaTimeHandler(event : HLSEvent) : void {
_position = Math.max(0, event.mediatime.position - _clip.start) ;
_duration = event.mediatime.duration - _clip.start;
// only update duration if not capped
if (!_durationCapped) {
_clip.duration = _duration;
_bufferedTime = Math.min(event.mediatime.buffer + _position, _duration);
} else {
// ensure capped duration is lt real one
_durationCapped = Math.min(_durationCapped, _duration);
_bufferedTime = Math.min(event.mediatime.buffer + _position, _durationCapped);
if (_durationCapped - _position <= 0.1) {
// reach end of stream, stop playback and simulate complete event
_hls.stream.close();
_clip.dispatchBeforeEvent(new ClipEvent(ClipEventType.FINISH));
_clip.startDispatched = false;
}
}
var videoWidth : int = _video.videoWidth;
var videoHeight : int = _video.videoHeight;
if (videoWidth && videoHeight) {
var changed : Boolean = _videoWidth != videoWidth || _videoHeight != videoHeight;
if (changed) {
CONFIG::LOGGING {
Log.info("video size changed to " + videoWidth + "/" + videoHeight);
}
_videoWidth = videoWidth;
_videoHeight = videoHeight;
_clip.originalWidth = videoWidth;
_clip.originalHeight = videoHeight;
if (!_clip.startDispatched) {
_clip.dispatch(ClipEventType.START);
_clip.startDispatched = true;
}
_clip.dispatch(ClipEventType.METADATA_CHANGED);
}
}
};
private function _stateHandler(event : HLSEvent) : void {
// CONFIG::LOGGING {
// Log.txt("state:"+ event.state);
// }
switch(event.state) {
case HLSPlayStates.IDLE:
case HLSPlayStates.PLAYING:
case HLSPlayStates.PAUSED:
_clip.dispatch(ClipEventType.BUFFER_FULL);
break;
case HLSPlayStates.PLAYING_BUFFERING:
case HLSPlayStates.PAUSED_BUFFERING:
_clip.dispatch(ClipEventType.BUFFER_EMPTY);
break;
default:
break;
}
};
/**
* Starts loading the specified clip. Once video data is available the provider
* must set it to the clip using <code>clip.setContent()</code>. Typically the video
* object passed to the clip is an instance of <a href="http://livedocs.adobe.com/flash/9.0/ActionScriptLangRefV3/flash/media/Video.html">flash.media.Video</a>.
*
* @param event the event that this provider should dispatch once loading has successfully started,
* once dispatched the player will call <code>getVideo()</code>
* @param clip the clip to load
* @param pauseAfterStart if <code>true</code> the playback is paused on first frame and
* buffering is continued
* @see Clip#setContent()
* @see #getVideo()
*/
public function load(event : ClipEvent, clip : Clip, pauseAfterStart : Boolean = true) : void {
_clip = clip;
CONFIG::LOGGING {
Log.info("load()" + clip.completeUrl);
Log.info("clip.start:" + clip.start);
Log.info("clip.duration:" + clip.duration);
}
_hls.load(clip.completeUrl);
_pauseAfterStart = pauseAfterStart;
_durationCapped = clip.duration;
clip.type = ClipType.VIDEO;
clip.dispatch(ClipEventType.BEGIN);
clip.setNetStream(_hls.stream);
return;
}
/**
* Gets the <a href="http://livedocs.adobe.com/flash/9.0/ActionScriptLangRefV3/flash/media/Video.html">Video</a> object.
* A stream will be attached to the returned video object using <code>attachStream()</code>.
* @param clip the clip for which the Video object is queried for
* @see #attachStream()
*/
public function getVideo(clip : Clip) : DisplayObject {
CONFIG::LOGGING {
Log.debug("getVideo()");
}
if (_video == null) {
if (clip.useStageVideo) {
CONFIG::LOGGING {
Log.debug("useStageVideo");
}
_video = new StageVideoWrapper(clip);
} else {
_video = new Video();
_video.smoothing = clip.smoothing;
}
}
return _video;
}
/**
* Attaches a stream to the specified display object.
* @param video the video object that was originally retrieved using <code>getVideo()</code>.
* @see #getVideo()
*/
public function attachStream(video : DisplayObject) : void {
CONFIG::LOGGING {
Log.debug("attachStream()");
}
Video(video).attachNetStream(_hls.stream);
return;
}
/**
* Pauses playback.
* @param event the event that this provider should dispatch once loading has been successfully paused
*/
public function pause(event : ClipEvent) : void {
CONFIG::LOGGING {
Log.info("pause()");
}
_hls.stream.pause();
if (event) {
_clip.dispatch(ClipEventType.PAUSE);
}
return;
}
/**
* Resumes playback.
* @param event the event that this provider should dispatch once loading has been successfully resumed
*/
public function resume(event : ClipEvent) : void {
CONFIG::LOGGING {
Log.info("resume()");
}
_hls.stream.resume();
_clip.dispatch(ClipEventType.RESUME);
return;
}
/**
* Stops and rewinds to the beginning of current clip.
* @param event the event that this provider should dispatch once loading has been successfully stopped
*/
public function stop(event : ClipEvent, closeStream : Boolean = false) : void {
CONFIG::LOGGING {
Log.info("stop()");
}
_hls.stream.close();
return;
}
/**
* Seeks to the specified point in the timeline.
* @param event the event that this provider should dispatch once the seek is in target
* @param seconds the target point in the timeline
*/
public function seek(event : ClipEvent, seconds : Number) : void {
CONFIG::LOGGING {
Log.info("seek()");
}
// real seek position : add clip.start offset
_hls.stream.seek(seconds + _clip.start);
_position = seconds;
_bufferedTime = seconds;
_clip.dispatch(ClipEventType.SEEK, seconds);
return;
}
/**
* File size in bytes.
*/
public function get fileSize() : Number {
return 0;
}
/**
* Current playhead time in seconds.
*/
public function get time() : Number {
return _position;
}
/**
* The point in timeline where the buffered data region begins, in seconds.
*/
public function get bufferStart() : Number {
return 0;
}
/**
* The point in timeline where the buffered data region ends, in seconds.
*/
public function get bufferEnd() : Number {
return _bufferedTime;
}
/**
* Does this provider support random seeking to unbuffered areas in the timeline?
*/
public function get allowRandomSeek() : Boolean {
// CONFIG::LOGGING {
// Log.info("allowRandomSeek()");
// }
return _seekable;
}
/**
* Volume controller used to control the video volume.
*/
public function set volumeController(controller : VolumeController) : void {
_volumecontroller = controller;
_volumecontroller.netStream = _hls.stream;
return;
}
/**
* Is this provider in the process of stopping the stream?
* When stopped the provider should not dispatch any events resulting from events that
* might get triggered by the underlying streaming implementation.
*/
public function get stopping() : Boolean {
CONFIG::LOGGING {
Log.info("stopping()");
}
return false;
}
/**
* The playlist instance.
*/
public function set playlist(playlist : Playlist) : void {
// CONFIG::LOGGING {
// Log.debug("set playlist()");
// }
_playlist = playlist;
return;
}
public function get playlist() : Playlist {
CONFIG::LOGGING {
Log.debug("get playlist()");
}
return _playlist;
}
/**
* Adds a callback public function to the NetConnection instance. This public function will fire ClipEvents whenever
* the callback is invoked in the connection.
* @param name
* @param listener
* @return
* @see ClipEventType#CONNECTION_EVENT
*/
public function addConnectionCallback(name : String, listener : Function) : void {
CONFIG::LOGGING {
Log.debug("addConnectionCallback()");
}
return;
}
/**
* Adds a callback public function to the NetStream object. This public function will fire a ClipEvent of type StreamEvent whenever
* the callback has been invoked on the stream. The invokations typically come from a server-side app running
* on RTMP server.
* @param name
* @param listener
* @return
* @see ClipEventType.NETSTREAM_EVENT
*/
public function addStreamCallback(name : String, listener : Function) : void {
CONFIG::LOGGING {
Log.debug("addStreamCallback()");
}
return;
}
/**
* Get the current stream callbacks.
* @return a dictionary of callbacks, keyed using callback names and values being the callback functions
*/
public function get streamCallbacks() : Dictionary {
CONFIG::LOGGING {
Log.debug("get streamCallbacks()");
}
return null;
}
/**
* Gets the underlying NetStream object.
* @return the netStream currently in use, or null if this provider has not started streaming yet
*/
public function get netStream() : NetStream {
CONFIG::LOGGING {
Log.debug("get netStream()");
}
return _hls.stream;
}
/**
* Gets the underlying netConnection object.
* @return the netConnection currently in use, or null if this provider has not started streaming yet
*/
public function get netConnection() : NetConnection {
CONFIG::LOGGING {
Log.debug("get netConnection()");
}
return null;
}
/**
* Sets a time provider to be used by this StreamProvider. Normally the playhead time is queried from
* the NetStream.time property.
*
* @param timeProvider
*/
public function set timeProvider(timeProvider : TimeProvider) : void {
CONFIG::LOGGING {
Log.debug("set timeProvider()");
}
_timeProvider = timeProvider;
return;
}
/**
* Gets the type of StreamProvider either http, rtmp, psuedo.
*/
public function get type() : String {
return "httpstreaming";
}
/**
* Switch the stream in realtime with / without dynamic stream switching support
*
* @param event ClipEvent the clip event
* @param clip Clip the clip to switch to
* @param netStreamPlayOptions Object the NetStreamPlayOptions object to enable dynamic stream switching
*/
public function switchStream(event : ClipEvent, clip : Clip, netStreamPlayOptions : Object = null) : void {
CONFIG::LOGGING {
Log.info("switchStream()");
}
return;
}
}
}