no commit message

This commit is contained in:
niandeng
2020-09-17 18:40:01 +08:00
parent 4382915d09
commit d957abceaa
283 changed files with 86935 additions and 63 deletions
+175
View File
@@ -0,0 +1,175 @@
/* 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 flash.utils.getTimer;
import flash.display.DisplayObject;
import flash.utils.ByteArray;
import flash.events.Event;
/**
* Contains Utility functions for AES-128 CBC Decryption
*/
public class AES {
private var _key : FastAESKey;
//private var _keyArray : ByteArray;
private var iv0 : uint;
private var iv1 : uint;
private var iv2 : uint;
private var iv3 : uint;
/* callback function upon decrypt progress */
private var _progress : Function;
/* callback function upon decrypt complete */
private var _complete : Function;
/** Byte data to be decrypt **/
private var _data : ByteArray;
/** read position **/
private var _readPosition : uint;
/** write position **/
private var _writePosition : uint;
/** chunk size to avoid blocking **/
private static const CHUNK_SIZE : uint = 2048;
/** is bytearray full ? **/
private var _dataComplete : Boolean;
/** display object used for ENTER_FRAME listener */
private var _displayObject : DisplayObject;
public function AES(displayObject : DisplayObject, key : ByteArray, iv : ByteArray, notifyprogress : Function, notifycomplete : Function) {
// _keyArray = key;
_key = new FastAESKey(key);
iv.position = 0;
iv0 = iv.readUnsignedInt();
iv1 = iv.readUnsignedInt();
iv2 = iv.readUnsignedInt();
iv3 = iv.readUnsignedInt();
_data = new ByteArray();
_dataComplete = false;
_progress = notifyprogress;
_complete = notifycomplete;
_readPosition = 0;
_writePosition = 0;
_displayObject = displayObject;
}
public function append(data : ByteArray) : void {
// CONFIG::LOGGING {
// Log.info("notify append");
// }
_data.position = _writePosition;
_data.writeBytes(data);
if (_writePosition == 0) {
_displayObject.addEventListener(Event.ENTER_FRAME, _decryptTimer);
}
_writePosition += data.length;
}
public function notifycomplete() : void {
// CONFIG::LOGGING {
// Log.info("notify complete");
// }
_dataComplete = true;
}
public function cancel() : void {
_displayObject.removeEventListener(Event.ENTER_FRAME, _decryptTimer);
}
private function _decryptTimer(e : Event) : void {
var start_time : int = getTimer();
var decrypted : Boolean;
do {
decrypted = _decryptChunk();
// dont spend more than 10ms in the decrypt timer to avoid blocking/freezing video
// if frame rate is 60fps, we have 1000/60 = 16.6ms budget total per frame
} while (decrypted && (getTimer() - start_time) < 10);
}
/** decrypt a small chunk of packets each time to avoid blocking **/
private function _decryptChunk() : Boolean {
_data.position = _readPosition;
var decryptdata : ByteArray;
if (_data.bytesAvailable) {
if (_data.bytesAvailable <= CHUNK_SIZE) {
if (_dataComplete) {
// CONFIG::LOGGING {
// Log.info("data complete, last chunk");
// }
_readPosition += _data.bytesAvailable;
decryptdata = _decryptCBC(_data, _data.bytesAvailable);
unpad(decryptdata);
} else {
// data not complete, and available data less than chunk size, return
return false;
}
} else {
_readPosition += CHUNK_SIZE;
decryptdata = _decryptCBC(_data, CHUNK_SIZE);
}
_progress(decryptdata);
return true;
} else {
if (_dataComplete) {
CONFIG::LOGGING {
Log.debug("AES:data+decrypt completed, callback");
}
// callback
_complete();
_displayObject.removeEventListener(Event.ENTER_FRAME, _decryptTimer);
}
return false;
}
}
/* Cypher Block Chaining Decryption, refer to
* http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher-block_chaining_
* for algorithm description
*/
private function _decryptCBC(crypt : ByteArray, len : uint) : ByteArray {
var src : Vector.<uint> = new Vector.<uint>(4);
var dst : Vector.<uint> = new Vector.<uint>(4);
var decrypt : ByteArray = new ByteArray();
decrypt.length = len;
for (var i : uint = 0; i < len / 16; i++) {
// read src byte array
src[0] = crypt.readUnsignedInt();
src[1] = crypt.readUnsignedInt();
src[2] = crypt.readUnsignedInt();
src[3] = crypt.readUnsignedInt();
// AES decrypt src vector into dst vector
_key.decrypt128(src, dst);
// CBC : write output = XOR(decrypted,IV)
decrypt.writeUnsignedInt(dst[0] ^ iv0);
decrypt.writeUnsignedInt(dst[1] ^ iv1);
decrypt.writeUnsignedInt(dst[2] ^ iv2);
decrypt.writeUnsignedInt(dst[3] ^ iv3);
// CBC : next IV = (input)
iv0 = src[0];
iv1 = src[1];
iv2 = src[2];
iv3 = src[3];
}
decrypt.position = 0;
return decrypt;
}
public function unpad(a : ByteArray) : void {
var c : uint = a.length % 16;
if (c != 0) throw new Error("PKCS#5::unpad: ByteArray.length isn't a multiple of the blockSize");
c = a[a.length - 1];
for (var i : uint = c; i > 0; i--) {
var v : uint = a[a.length - 1];
a.length--;
if (c != v) throw new Error("PKCS#5:unpad: Invalid padding value. expected [" + c + "], found [" + v + "]");
}
}
public function destroy() : void {
_key.dispose();
// _key = null;
}
}
}
+75
View File
@@ -0,0 +1,75 @@
/**
* DateUtil
*
* inspired by https://code.google.com/p/as3corelib/source/browse/trunk/src/com/adobe/utils/DateUtil.as#531
*/
package org.mangui.hls.utils {
public class DateUtil {
public static function parseW3CDTF(str:String):Date
{
var finalDate:Date;
try
{
var dateStr:String = str.substring(0, str.indexOf("T"));
var timeStr:String = str.substring(str.indexOf("T")+1, str.length);
var dateArr:Array = dateStr.split("-");
var year:Number = Number(dateArr.shift());
var month:Number = Number(dateArr.shift());
var date:Number = Number(dateArr.shift());
var multiplier:Number;
var offsetHours:Number;
var offsetMinutes:Number;
var offsetStr:String;
if (timeStr.indexOf("Z") != -1)
{
multiplier = 1;
offsetHours = 0;
offsetMinutes = 0;
timeStr = timeStr.replace("Z", "");
}
else if (timeStr.indexOf("+") != -1)
{
multiplier = 1;
offsetStr = timeStr.substring(timeStr.indexOf("+")+1, timeStr.length);
offsetHours = Number(offsetStr.substring(0, offsetStr.indexOf(":")));
offsetMinutes = Number(offsetStr.substring(offsetStr.indexOf(":")+1, offsetStr.length));
timeStr = timeStr.substring(0, timeStr.indexOf("+"));
}
else // offset is -
{
multiplier = -1;
offsetStr = timeStr.substring(timeStr.indexOf("-")+1, timeStr.length);
offsetHours = Number(offsetStr.substring(0, offsetStr.indexOf(":")));
offsetMinutes = Number(offsetStr.substring(offsetStr.indexOf(":")+1, offsetStr.length));
timeStr = timeStr.substring(0, timeStr.indexOf("-"));
}
var timeArr:Array = timeStr.split(":");
var hour:Number = Number(timeArr.shift());
var minutes:Number = Number(timeArr.shift());
var secondsArr:Array = (timeArr.length > 0) ? String(timeArr.shift()).split(".") : null;
var seconds:Number = (secondsArr != null && secondsArr.length > 0) ? Number(secondsArr.shift()) : 0;
//var milliseconds:Number = (secondsArr != null && secondsArr.length > 0) ? Number(secondsArr.shift()) : 0;
var milliseconds:Number = (secondsArr != null && secondsArr.length > 0) ? 1000*parseFloat("0." + secondsArr.shift()) : 0;
var utc:Number = Date.UTC(year, month-1, date, hour, minutes, seconds, milliseconds);
var offset:Number = (((offsetHours * 3600000) + (offsetMinutes * 60000)) * multiplier);
finalDate = new Date(utc - offset);
if (finalDate.toString() == "Invalid Date")
{
throw new Error("This date does not conform to W3CDTF.");
}
}
catch (e:Error)
{
var eStr:String = "Unable to parse the string [" +str+ "] into a date. ";
eStr += "The internal error was: " + e.toString();
throw new Error(eStr);
}
return finalDate;
}
}
}
+202
View File
@@ -0,0 +1,202 @@
/* 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 flash.utils.ByteArray;
/* word based AES encryption/decryption
* inspired by
* https://code.google.com/p/crypto-js/source/browse/tags/3.1.2/src/aes.js
*/
public class FastAESKey {
/* private data, specific to each key */
private var keySize : uint;
private var nRounds : uint;
private var ksRows : uint;
private var keySchedule : Vector.<uint>;
private var invKeySchedule : Vector.<uint>;
private var keyWords : Vector.<uint>;
private var state : Vector.<uint>;
// static Lookup tables
private static var _SBOX : Vector.<uint>;
private static var _INV_SBOX : Vector.<uint>;
private static var _INV_SUB_MIX_0 : Vector.<uint>;
private static var _INV_SUB_MIX_1 : Vector.<uint>;
private static var _INV_SUB_MIX_2 : Vector.<uint>;
private static var _INV_SUB_MIX_3 : Vector.<uint>;
private static var _RCON : Vector.<uint>;
// static initializer
{
_initTable();
};
private static function _initTable() : void {
_SBOX = new Vector.<uint>(256);
_INV_SBOX = new Vector.<uint>(256);
_INV_SUB_MIX_0 = new Vector.<uint>(256);
_INV_SUB_MIX_1 = new Vector.<uint>(256);
_INV_SUB_MIX_2 = new Vector.<uint>(256);
_INV_SUB_MIX_3 = new Vector.<uint>(256);
_RCON = new <uint>[0x0, 0x1, 0x2, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36];
// Compute double table
var i : int;
var d : Vector.<uint> = new Vector.<uint>(256);
for (i = 0; i < 256; i++) {
if (i < 128) {
d[i] = i << 1;
} else {
d[i] = (i << 1) ^ 0x11b;
}
}
// Walk GF(2^8)
var x : uint = 0;
var xi : uint = 0;
for (i = 0; i < 256; i++) {
// Compute sbox
var sx : uint = xi ^ (xi << 1) ^ (xi << 2) ^ (xi << 3) ^ (xi << 4);
sx = (sx >>> 8) ^ (sx & 0xff) ^ 0x63;
_SBOX[x] = sx;
_INV_SBOX[sx] = x;
// Compute multiplication
var x2 : uint = d[x];
var x4 : uint = d[x2];
var x8 : uint = d[x4];
// Compute inv sub bytes, inv mix columns tables
var t: uint = (x8 * 0x1010101) ^ (x4 * 0x10001) ^ (x2 * 0x101) ^ (x * 0x1010100);
_INV_SUB_MIX_0[sx] = (t << 24) | (t >>> 8);
_INV_SUB_MIX_1[sx] = (t << 16) | (t >>> 16);
_INV_SUB_MIX_2[sx] = (t << 8) | (t >>> 24);
_INV_SUB_MIX_3[sx] = t;
// Compute next counter
if (!x) {
x = xi = 1;
} else {
x = x2 ^ d[d[d[x8 ^ x2]]];
xi ^= d[d[xi]];
}
}
}
public function FastAESKey(key : ByteArray) {
keySize = key.length / 4;
// Compute number of rounds
nRounds = keySize + 6;
// Compute number of key schedule rows
ksRows = (nRounds + 1) * 4;
state = new Vector.<uint>(keySize);
keyWords = new Vector.<uint>(keySize);
key.position = 0;
for (var i : int = 0; i < keySize; i++) {
keyWords[i] = key.readUnsignedInt();
}
expandKey();
}
private function expandKey() : void {
this.keySchedule = new Vector.<uint>(ksRows);
for (var ksRow : int = 0; ksRow < ksRows; ksRow++) {
if (ksRow < keySize) {
keySchedule[ksRow] = keyWords[ksRow];
} else {
var t : uint = keySchedule[ksRow - 1];
if (!(ksRow % keySize)) {
// Rot word
t = (t << 8) | (t >>> 24);
// Sub word
t = (_SBOX[t >>> 24] << 24) | (_SBOX[(t >>> 16) & 0xff] << 16) | (_SBOX[(t >>> 8) & 0xff] << 8) | _SBOX[t & 0xff];
// Mix Rcon
t ^= _RCON[(ksRow / keySize) | 0] << 24;
} else if (keySize > 6 && ksRow % keySize == 4) {
// Sub word
t = (_SBOX[t >>> 24] << 24) | (_SBOX[(t >>> 16) & 0xff] << 16) | (_SBOX[(t >>> 8) & 0xff] << 8) | _SBOX[t & 0xff];
}
keySchedule[ksRow] = keySchedule[ksRow - keySize] ^ t;
}
}
// Compute inv key schedule
this.invKeySchedule = new Vector.<uint>(ksRows);
for (var invKsRow : int = 0; invKsRow < ksRows; invKsRow++) {
ksRow = ksRows - invKsRow;
if (invKsRow % 4) {
t = keySchedule[ksRow];
} else {
t = keySchedule[ksRow - 4];
}
if (invKsRow < 4 || ksRow <= 4) {
invKeySchedule[invKsRow] = t;
} else {
invKeySchedule[invKsRow] = _INV_SUB_MIX_0[_SBOX[t >>> 24]] ^ _INV_SUB_MIX_1[_SBOX[(t >>> 16) & 0xff]] ^ _INV_SUB_MIX_2[_SBOX[(t >>> 8) & 0xff]] ^ _INV_SUB_MIX_3[_SBOX[t & 0xff]];
}
}
}
public function decrypt128(input : Vector.<uint>,output : Vector.<uint>) : void {
// Swap 2nd and 4th rows
state[0] = input[0];
state[1] = input[3];
state[2] = input[2];
state[3] = input[1];
_doCryptBlock(invKeySchedule, _INV_SUB_MIX_0, _INV_SUB_MIX_1, _INV_SUB_MIX_2, _INV_SUB_MIX_3, _INV_SBOX);
// Inv swap 2nd and 4th rows
output[0] = state[0];
output[1] = state[3];
output[2] = state[2];
output[3] = state[1];
}
private function _doCryptBlock(keySchedule : Vector.<uint>, SUB_MIX_0 : Vector.<uint>, SUB_MIX_1 : Vector.<uint>, SUB_MIX_2 : Vector.<uint>, SUB_MIX_3 : Vector.<uint>, SBOX : Vector.<uint>) : void {
// Shortcut
// Get input, add round key
var s0 : uint = state[0] ^ keySchedule[0];
var s1 : uint = state[1] ^ keySchedule[1];
var s2 : uint = state[2] ^ keySchedule[2];
var s3 : uint = state[3] ^ keySchedule[3];
// Key schedule row counter
var ksRow : uint = 4;
var t0 : uint;
var t1 : uint;
var t2 : uint;
var t3 : uint;
// Rounds
for (var round : int = 1; round < nRounds; round++) {
// Shift rows, sub bytes, mix columns, add round key
t0 = SUB_MIX_0[s0 >>> 24] ^ SUB_MIX_1[(s1 >>> 16) & 0xff] ^ SUB_MIX_2[(s2 >>> 8) & 0xff] ^ SUB_MIX_3[s3 & 0xff] ^ keySchedule[ksRow++];
t1 = SUB_MIX_0[s1 >>> 24] ^ SUB_MIX_1[(s2 >>> 16) & 0xff] ^ SUB_MIX_2[(s3 >>> 8) & 0xff] ^ SUB_MIX_3[s0 & 0xff] ^ keySchedule[ksRow++];
t2 = SUB_MIX_0[s2 >>> 24] ^ SUB_MIX_1[(s3 >>> 16) & 0xff] ^ SUB_MIX_2[(s0 >>> 8) & 0xff] ^ SUB_MIX_3[s1 & 0xff] ^ keySchedule[ksRow++];
t3 = SUB_MIX_0[s3 >>> 24] ^ SUB_MIX_1[(s0 >>> 16) & 0xff] ^ SUB_MIX_2[(s1 >>> 8) & 0xff] ^ SUB_MIX_3[s2 & 0xff] ^ keySchedule[ksRow++];
// Update state
s0 = t0;
s1 = t1;
s2 = t2;
s3 = t3;
}
// Shift rows, sub bytes, add round key
t0 = ((SBOX[s0 >>> 24] << 24) | (SBOX[(s1 >>> 16) & 0xff] << 16) | (SBOX[(s2 >>> 8) & 0xff] << 8) | SBOX[s3 & 0xff]) ^ keySchedule[ksRow++];
t1 = ((SBOX[s1 >>> 24] << 24) | (SBOX[(s2 >>> 16) & 0xff] << 16) | (SBOX[(s3 >>> 8) & 0xff] << 8) | SBOX[s0 & 0xff]) ^ keySchedule[ksRow++];
t2 = ((SBOX[s2 >>> 24] << 24) | (SBOX[(s3 >>> 16) & 0xff] << 16) | (SBOX[(s0 >>> 8) & 0xff] << 8) | SBOX[s1 & 0xff]) ^ keySchedule[ksRow++];
t3 = ((SBOX[s3 >>> 24] << 24) | (SBOX[(s0 >>> 16) & 0xff] << 16) | (SBOX[(s1 >>> 8) & 0xff] << 8) | SBOX[s2 & 0xff]) ^ keySchedule[ksRow++];
// Set output
state[0] = t0;
state[1] = t1;
state[2] = t2;
state[3] = t3;
}
public function dispose() : void {
keyWords.length = 0;
keyWords = null;
}
}
}
+46
View File
@@ -0,0 +1,46 @@
/**
* Hex
*
* Utility class to convert Hex strings to ByteArray or String types.
* Copyright (c) 2007 Henri Torgemane
*
* See LICENSE.txt for full license information.
*/
package org.mangui.hls.utils {
import flash.utils.ByteArray;
public class Hex {
/**
* Generates byte-array from given hexadecimal string
*
* Supports straight and colon-laced hex (that means 23:03:0e:f0, but *NOT* 23:3:e:f0)
* The first nibble (hex digit) may be omitted.
* Any whitespace characters are ignored.
*/
public static function toArray(hex : String) : ByteArray {
hex = hex.replace(/^0x|\s|:/gm, '');
var a : ByteArray = new ByteArray;
var len : uint = hex.length;
if ((len & 1) == 1) hex = "0" + hex;
for (var i : uint = 0; i < len; i += 2) {
a[i / 2] = parseInt(hex.substr(i, 2), 16);
}
return a;
}
/**
* Generates lowercase hexadecimal string from given byte-array
*/
public static function fromArray(array : ByteArray, colons : Boolean = false) : String {
var s : String = "";
var len : uint = array.length;
for (var i : uint = 0; i < len; i++) {
s += ("0" + array[i].toString(16)).substr(-2, 2);
if (colons) {
if (i < len - 1) s += ":";
}
}
return s;
}
}
}
@@ -0,0 +1,97 @@
/* 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 org.mangui.hls.HLS;
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.URLLoader;
CONFIG::LOGGING {
import org.mangui.hls.utils.Log;
}
// Playlist Loader
public dynamic class JSURLLoader extends URLLoader {
private var _resource : String = new String();
/* callback names */
private var _callbackLoaded : String;
private var _callbackFailure : String;
private static var _instanceCount : int = 0;
/** JS callbacks prefix */
protected static var _callbackName : String = 'JSLoaderPlaylist';
public function JSURLLoader() {
ExternalInterface.marshallExceptions = true;
super();
// Connect calls to JS.
if (ExternalInterface.available) {
CONFIG::LOGGING {
Log.debug("add callback textLoaded, id:" + _instanceCount);
}
_callbackLoaded = "textLoaded" + _instanceCount;
_callbackFailure = "textLoadingError" + _instanceCount;
// dynamically register callbacks
this[_callbackLoaded] = function(res:String): void { resourceLoaded(res)};
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 close() : void {
if (ExternalInterface.available) {
_trigger('abortPlaylist', ExternalInterface.objectID);
} else {
super.close();
}
}
override public function load(request : URLRequest) : void {
CONFIG::LOGGING {
Log.debug("JSURLLoader.load:" + request.url);
}
bytesLoaded = bytesTotal = 0;
data = null;
if (ExternalInterface.available) {
_trigger('requestPlaylist', ExternalInterface.objectID, request.url, _callbackLoaded, _callbackFailure);
this.dispatchEvent(new Event(Event.OPEN));
} else {
super.load(request);
}
}
protected function resourceLoaded(resource : String) : void {
CONFIG::LOGGING {
Log.debug("resourceLoaded");
}
data = resource;
bytesLoaded = bytesTotal = resource.length;
this.dispatchEvent(new ProgressEvent(ProgressEvent.PROGRESS, false, false, bytesLoaded, bytesTotal));
this.dispatchEvent(new Event(Event.COMPLETE));
}
protected function resourceLoadingError() : void {
CONFIG::LOGGING {
Log.debug("resourceLoadingError");
}
this.dispatchEvent(new IOErrorEvent(IOErrorEvent.IO_ERROR));
}
}
}
+182
View File
@@ -0,0 +1,182 @@
/* 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();
}
}
}
}
+48
View File
@@ -0,0 +1,48 @@
/* 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 flash.external.ExternalInterface;
import org.mangui.hls.HLSSettings;
/** Class that sends log messages to browser console. **/
public class Log {
private static const LEVEL_INFO : String = "INFO:";
private static const LEVEL_DEBUG : String = "DEBUG:";
private static const LEVEL_WARN : String = "WARN:";
private static const LEVEL_ERROR : String = "ERROR:";
public static function info(message : *) : void {
if (HLSSettings.logInfo)
outputlog(LEVEL_INFO, String(message));
};
public static function debug(message : *) : void {
if (HLSSettings.logDebug)
outputlog(LEVEL_DEBUG, String(message));
};
public static function debug2(message : *) : void {
if (HLSSettings.logDebug2)
outputlog(LEVEL_DEBUG, String(message));
};
public static function warn(message : *) : void {
if (HLSSettings.logWarn)
outputlog(LEVEL_WARN, String(message));
};
public static function error(message : *) : void {
if (HLSSettings.logError)
outputlog(LEVEL_ERROR, String(message));
};
/** Log a message to the console. **/
private static function outputlog(level : String, message : String) : void {
if (ExternalInterface.available)
ExternalInterface.call('console.log', level + message);
else trace(level + message);
}
};
}
+33
View File
@@ -0,0 +1,33 @@
/* 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 {
public class PTS {
/* find PTS value nearest a given reference PTS value
*
* PTS retrieved from demux are within a range of
* (+/-) 2^32/90 - 1 = (+/-) 47721858
* when reaching upper limit, PTS will loop to lower limit
* this cause some issues with fragment duration calculation
* this method will normalize a given PTS value and output a result
* that is closest to provided PTS reference value.
* i.e it could output values bigger than the (+/-) 2^32/90.
* this will avoid PTS looping issues.
*/
public static function normalize(reference : Number, value : Number) : Number {
var offset : Number;
if (reference < value) {
// - 2^33/90
offset = -95443717;
} else {
// + 2^33/90
offset = 95443717;
}
// 2^32 / 90
while (!isNaN(reference) && (Math.abs(value - reference) > 47721858)) {
value += offset;
}
return value;
}
}
}
@@ -0,0 +1,76 @@
/* 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 org.mangui.hls.HLSSettings;
import flash.utils.describeType;
import flash.utils.Dictionary;
import flash.utils.getDefinitionByName;
import flash.utils.getQualifiedClassName;
/**
* Params2Settings is an helper class that holds every legal external params names
* which can be used to customize HLSSettings and maps them to the relevant HLSSettings values
*/
public class Params2Settings {
/**
* HLSSettings <-> params maping
*/
private static var _paramMap : Dictionary = new Dictionary();
// static initializer
{
_initParams();
}
/* build map between param name and HLSSettings property
this is done by enumerating properties : http://stackoverflow.com/questions/13294997/as3-iterating-through-class-variables
*/
private static function _initParams() : void {
var description:XML = describeType(HLSSettings);
var variables:XMLList = description..variable;
for each(var variable:XML in variables) {
var name : String = variable.@name;
var param : String;
if(name.indexOf("log") == 0) {
// loggers params don't need prefix
param = name.substr(3);
} else {
param = name;
}
// for historical (bad ?) reasons, param names are lowercase
param = param.toLowerCase();
_paramMap[param] = name;
}
}
public static function set(key : String, value : Object) : void {
var param : String = _paramMap[key];
if (param) {
// try to assign value with proper object type
try {
var cName : String = getQualifiedClassName(HLSSettings[param]);
// AS3 bug: "getDefinitionByName" considers var value, not type, and wrongly (e.g. 3.0 >> "int"; 3.1 >> "Number").
var c : Class = cName === "int" ? Number : getDefinitionByName(cName) as Class;
// only set booleans to true if the string actually is true, otherwise false
if(cName === "Boolean") {
HLSSettings[param] = String(value).toLowerCase() === "true";
} else { // Otherwise just cast it
HLSSettings[param] = c(value);
}
CONFIG::LOGGING {
Log.info("HLSSettings." + param + " = " + HLSSettings[param]);
}
} catch(error : Error) {
CONFIG::LOGGING {
Log.warn("Can't set HLSSettings." + param);
}
}
}
}
}
}
+32
View File
@@ -0,0 +1,32 @@
/* 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 flash.geom.Rectangle;
public class ScaleVideo {
public static function resizeRectangle(videoWidth : int, videoHeight : int, containerWidth : int, containerHeight : int) : Rectangle {
var rect : Rectangle = new Rectangle();
var xscale : Number = containerWidth / videoWidth;
var yscale : Number = containerHeight / videoHeight;
if (xscale >= yscale) {
rect.width = Math.min(videoWidth * yscale, containerWidth);
rect.height = videoHeight * yscale;
} else {
rect.width = Math.min(videoWidth * xscale, containerWidth);
rect.height = videoHeight * xscale;
}
rect.width = Math.ceil(rect.width);
rect.height = Math.ceil(rect.height);
rect.x = Math.round((containerWidth - rect.width) / 2);
rect.y = Math.round((containerHeight - rect.height) / 2);
CONFIG::LOGGING {
Log.debug("width:" + rect.width);
Log.debug("height:" + rect.height);
Log.debug("x:" + rect.x);
Log.debug("y:" + rect.y);
}
return rect;
}
}
}
+94
View File
@@ -0,0 +1,94 @@
package org.mangui.hls.utils
{
public class StringUtil
{
/**
* Returns <code>true</code> if the specified string is
* a single space, tab, carriage return, newline, or formfeed character.
*
* @param str The String that is is being queried.
*
* @return <code>true</code> if the specified string is
* a single space, tab, carriage return, newline, or formfeed character.
*/
public static function isWhitespace(character:String):Boolean
{
switch (character)
{
case " ":
case "\t":
case "\r":
case "\n":
case "\f":
case "\u00A0": // non breaking space
case "\u2028": // line seperator
case "\u2029": // paragraph seperator
case "\u3000": // ideographic space
return true;
}
return false;
}
/**
* Removes all whitespace characters from the beginning and end
* of the specified string.
*
* @param str The String whose whitespace should be trimmed.
*
* @return Updated String where whitespace was removed from the
* beginning and end.
*/
public static function trim(str:String):String
{
if (str == null) return '';
var startIndex:int = 0;
while (isWhitespace(str.charAt(startIndex)))
++startIndex;
var endIndex:int = str.length - 1;
while (isWhitespace(str.charAt(endIndex)))
--endIndex;
if (endIndex >= startIndex)
return str.slice(startIndex, endIndex + 1);
else
return "";
}
/**
* Splits a String into an Vector of nicely trimmed strings, where each
* item represents a single line of the original String data
*
* @param str The String to be split into an Array
* @return Vector of type String
*/
public static function toLines(str:String):Vector.<String>
{
var lines:Array = toLF(str).split("\n");
var i:uint;
var length:uint = lines.length;
for (i=0; i<length; ++i)
{
lines[i] = trim(lines[i]);
}
return Vector.<String>(lines);
}
/**
* Converts strings containing Windows (CR-LF), MacOS (CR) and other
* non-standard line breaks (LF-CR) into strings using only Linux-style
* line breaks (LF).
*
* @param str String containing non-Linux line breaks
* @returns String containly only Linux-style line breaks
*/
public static function toLF(str:String):String
{
return (str || "").replace(/\r\n|\n\r|\r/g, "\n");
}
}
}