183 lines
6.8 KiB
ActionScript
183 lines
6.8 KiB
ActionScript
/* 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.hls.utils {
|
|
import by.blooddy.crypto.Base64;
|
|
import flash.events.Event;
|
|
import flash.events.IOErrorEvent;
|
|
import flash.events.ProgressEvent;
|
|
import flash.events.TimerEvent;
|
|
import flash.external.ExternalInterface;
|
|
import flash.net.URLRequest;
|
|
import flash.net.URLStream;
|
|
import flash.utils.ByteArray;
|
|
import flash.utils.getTimer;
|
|
import flash.utils.Timer;
|
|
|
|
CONFIG::LOGGING {
|
|
import org.mangui.hls.utils.Log;
|
|
}
|
|
|
|
// Fragment Loader
|
|
public dynamic class JSURLStream extends URLStream {
|
|
private var _connected : Boolean;
|
|
private var _resource : ByteArray = new ByteArray();
|
|
/** Timer for decode packets **/
|
|
private var _timer : Timer;
|
|
/** base64 read position **/
|
|
private var _readPosition : uint;
|
|
/** final length **/
|
|
private var _finalLength : uint;
|
|
/** read position **/
|
|
private var _base64Resource : String;
|
|
/* callback names */
|
|
private var _callbackLoaded : String;
|
|
private var _callbackFailure : String;
|
|
/** chunk size to avoid blocking **/
|
|
private static const CHUNK_SIZE : uint = 65536;
|
|
private static var _instanceCount : int = 0;
|
|
/** JS callbacks prefix */
|
|
protected static var _callbackName : String = 'JSLoaderFragment';
|
|
|
|
public function JSURLStream() {
|
|
addEventListener(Event.OPEN, onOpen);
|
|
ExternalInterface.marshallExceptions = true;
|
|
super();
|
|
|
|
// Connect calls to JS.
|
|
if (ExternalInterface.available) {
|
|
CONFIG::LOGGING {
|
|
Log.debug("add callback resourceLoaded, id:" + _instanceCount);
|
|
}
|
|
_callbackLoaded = "resourceLoaded" + _instanceCount;
|
|
_callbackFailure = "resourceLoadingError" + _instanceCount;
|
|
// dynamically register callbacks
|
|
this[_callbackLoaded] = function(res:String,len:uint): void { resourceLoaded(res,len)};
|
|
this[_callbackFailure] = function() : void { resourceLoadingError()};
|
|
ExternalInterface.addCallback(_callbackLoaded, this[_callbackLoaded]);
|
|
ExternalInterface.addCallback(_callbackFailure, this[_callbackFailure]);
|
|
_instanceCount++;
|
|
}
|
|
}
|
|
|
|
public static function set externalCallback(callbackName: String) : void {
|
|
_callbackName = callbackName;
|
|
}
|
|
|
|
protected function _trigger(event : String, ...args) : void {
|
|
if (ExternalInterface.available) {
|
|
ExternalInterface.call(_callbackName, event, args);
|
|
}
|
|
}
|
|
|
|
override public function get connected() : Boolean {
|
|
return _connected;
|
|
}
|
|
|
|
override public function get bytesAvailable() : uint {
|
|
return _resource.bytesAvailable;
|
|
}
|
|
|
|
override public function readByte() : int {
|
|
return _resource.readByte();
|
|
}
|
|
|
|
override public function readUnsignedShort() : uint {
|
|
return _resource.readUnsignedShort();
|
|
}
|
|
|
|
override public function readBytes(bytes : ByteArray, offset : uint = 0, length : uint = 0) : void {
|
|
_resource.readBytes(bytes, offset, length);
|
|
}
|
|
|
|
override public function close() : void {
|
|
if(_timer) {
|
|
_timer.stop();
|
|
_timer.removeEventListener(TimerEvent.TIMER, _decodeData);
|
|
}
|
|
if (ExternalInterface.available) {
|
|
_trigger('abortFragment', ExternalInterface.objectID);
|
|
} else {
|
|
super.close();
|
|
}
|
|
}
|
|
|
|
override public function load(request : URLRequest) : void {
|
|
CONFIG::LOGGING {
|
|
Log.debug("JSURLStream.load:" + request.url);
|
|
}
|
|
if (ExternalInterface.available) {
|
|
_trigger('requestFragment', ExternalInterface.objectID, request.url, _callbackLoaded, _callbackFailure);
|
|
this.dispatchEvent(new Event(Event.OPEN));
|
|
} else {
|
|
super.load(request);
|
|
}
|
|
}
|
|
|
|
private function onOpen(event : Event) : void {
|
|
_connected = true;
|
|
}
|
|
|
|
protected function resourceLoaded(base64Resource : String, len : uint) : void {
|
|
CONFIG::LOGGING {
|
|
Log.debug("resourceLoaded");
|
|
}
|
|
_resource = new ByteArray();
|
|
_readPosition = 0;
|
|
_finalLength = len;
|
|
_timer = new Timer(20, 0);
|
|
_timer.addEventListener(TimerEvent.TIMER, _decodeData);
|
|
_timer.start();
|
|
_base64Resource = base64Resource;
|
|
}
|
|
|
|
protected function resourceLoadingError() : void {
|
|
CONFIG::LOGGING {
|
|
Log.debug("resourceLoadingError");
|
|
}
|
|
if(_timer) {
|
|
_timer.stop();
|
|
}
|
|
this.dispatchEvent(new IOErrorEvent(IOErrorEvent.IO_ERROR));
|
|
}
|
|
|
|
protected function resourceLoadingSuccess() : void {
|
|
CONFIG::LOGGING {
|
|
Log.debug("resourceLoaded and decoded");
|
|
}
|
|
_timer.stop();
|
|
this.dispatchEvent(new Event(Event.COMPLETE));
|
|
}
|
|
|
|
/** decrypt a small chunk of packets each time to avoid blocking **/
|
|
private function _decodeData(e : Event) : void {
|
|
var startTime : int = getTimer();
|
|
var decodeCompleted : Boolean = false;
|
|
// dont spend more than 20ms base64 decoding to avoid fps drop
|
|
while ((!decodeCompleted) && ((getTimer() - startTime) < 10)) {
|
|
var startPos : uint = _readPosition,endPos : uint;
|
|
if (_base64Resource.length <= _readPosition + CHUNK_SIZE) {
|
|
endPos = _base64Resource.length;
|
|
decodeCompleted = true;
|
|
} else {
|
|
endPos = _readPosition + CHUNK_SIZE;
|
|
_readPosition = endPos;
|
|
}
|
|
var tmpString : String = _base64Resource.substring(startPos, endPos);
|
|
var savePosition : uint = _resource.position;
|
|
try {
|
|
_resource.position = _resource.length;
|
|
_resource.writeBytes(Base64.decode(tmpString));
|
|
_resource.position = savePosition;
|
|
} catch (error:Error) {
|
|
resourceLoadingError();
|
|
}
|
|
}
|
|
this.dispatchEvent(new ProgressEvent(ProgressEvent.PROGRESS, false, false, _resource.length, _finalLength));
|
|
if (decodeCompleted) {
|
|
resourceLoadingSuccess();
|
|
}
|
|
}
|
|
}
|
|
}
|