Skip to content

Commit

Permalink
support HLS time metadata carried in PES packet
Browse files Browse the repository at this point in the history
we look for PID with stream type = 0x15 in PMT as documented in https://developer.apple.com/library/ios/documentation/AudioVideo/Conceptual/HTTP_Live_Streaming_Metadata_Spec/HTTP_Live_Streaming_Metadata_Spec.pdf
HLSEvent.ID3_UPDATED will be triggered with corresponding ID3 tag.
related to #76
  • Loading branch information
mangui committed Oct 20, 2014
1 parent 1cbd963 commit 2cd5287
Show file tree
Hide file tree
Showing 12 changed files with 145 additions and 4 deletions.
Binary file modified bin/debug/flashls.swc
Binary file not shown.
Binary file modified bin/debug/flashlsChromeless.swf
Binary file not shown.
Binary file modified bin/debug/flashlsFlowPlayer.swf
Binary file not shown.
Binary file modified bin/debug/flashlsOSMF.swc
Binary file not shown.
Binary file modified bin/debug/flashlsOSMF.swf
Binary file not shown.
Binary file modified bin/release/flashls.swc
Binary file not shown.
Binary file modified bin/release/flashlsOSMF.swc
Binary file not shown.
5 changes: 5 additions & 0 deletions src/org/mangui/flowplayer/HLSStreamProvider.as
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ package org.mangui.flowplayer {
_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) {
Expand All @@ -91,6 +92,10 @@ package org.mangui.flowplayer {
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;
_isManifestLoaded = true;
Expand Down
100 changes: 99 additions & 1 deletion src/org/mangui/hls/demux/TSDemuxer.as
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ package org.mangui.hls.demux {
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.utils.ByteArray;
import flash.net.ObjectEncoding;

CONFIG::LOGGING {
import org.mangui.hls.utils.Log;
Expand Down Expand Up @@ -39,6 +40,8 @@ package org.mangui.hls.demux {
/** audio PID **/
private var _audioId : int;
private var _audioIsAAC : Boolean;
/** ID3 PID **/
private var _id3Id : int;
/** Vector of audio/video tags **/
private var _tags : Vector.<FLVTag>;
/** Display Object used to schedule parsing **/
Expand All @@ -54,6 +57,8 @@ package org.mangui.hls.demux {
private var _curAudioPES : ByteArray;
/* current video PES */
private var _curVideoPES : ByteArray;
/* current id3 PES */
private var _curId3PES : ByteArray;
/* ADTS frame overflow */
private var _adtsFrameOverflow : ByteArray;
/* current NAL unit */
Expand Down Expand Up @@ -90,6 +95,7 @@ package org.mangui.hls.demux {
public function TSDemuxer(displayObject : DisplayObject, callback_audioselect : Function, callback_progress : Function, callback_complete : Function, callback_videometadata : Function) {
_curAudioPES = null;
_curVideoPES = null;
_curId3PES = null;
_curVideoTag = null;
_curNalUnit = null;
_adtsFrameOverflow = null;
Expand All @@ -99,7 +105,7 @@ package org.mangui.hls.demux {
_callback_videometadata = callback_videometadata;
_pmtParsed = false;
_packetsBeforePMT = false;
_pmtId = _avcId = _audioId = -1;
_pmtId = _avcId = _audioId = _id3Id = -1;
_audioIsAAC = false;
_tags = new Vector.<FLVTag>();
_displayObject = displayObject;
Expand All @@ -126,6 +132,7 @@ package org.mangui.hls.demux {
_data = null;
_curAudioPES = null;
_curVideoPES = null;
_curId3PES = null;
_curVideoTag = null;
_curNalUnit = null;
_adtsFrameOverflow = null;
Expand Down Expand Up @@ -222,6 +229,23 @@ package org.mangui.hls.demux {
_curVideoPES.position = _curVideoPES.length;
}
}
// check whether last parsed ID3 PES is complete
if (_curId3PES && _curId3PES.length > 14) {
var pes3 : PES = new PES(_curId3PES, false);
if (pes3.len && (pes3.data.length - pes3.payload - pes3.payload_len) >= 0) {
CONFIG::LOGGING {
Log.debug2("TS: complete ID3 PES found at end of segment, parse it");
}
// complete PES, parse and push into the queue
_parseID3PES(pes3);
_curId3PES = null;
} else {
CONFIG::LOGGING {
Log.debug("TS: partial ID3 PES at end of segment");
}
_curId3PES.position = _curId3PES.length;
}
}
// push remaining tags and notify complete
if (_tags.length) {
CONFIG::LOGGING {
Expand Down Expand Up @@ -457,6 +481,42 @@ package org.mangui.hls.demux {
return false;
}

/** parse ID3 PES packet **/
private function _parseID3PES(pes : PES) : void {
// note: apple spec does not include having PTS in ID3!!!!
// so we should really spoof the PTS by knowing the PCR at this point
if (isNaN(pes.pts)) {
CONFIG::LOGGING {
Log.warn("TS: no PTS info in this ID3 PES packet,discarding it");
}
return;
}

var pespayload : ByteArray = new ByteArray();
if (pes.data.length >= pes.payload + pes.payload_len) {
pes.data.position = pes.payload;
pespayload.writeBytes(pes.data, pes.payload, pes.payload_len);
pespayload.position = 0;
}
pes.data.position = 0;

var tag : FLVTag = new FLVTag(FLVTag.METADATA, pes.pts, pes.pts, false);
var data : ByteArray = new ByteArray();
data.objectEncoding = ObjectEncoding.AMF0;

// one or more SCRIPTDATASTRING + SCRIPTDATAVALUE
data.writeObject("onID3Data");
// SCRIPTDATASTRING - name of object
// to pass ByteArray, change to AMF3
data.objectEncoding = ObjectEncoding.AMF3;
data.writeByte(0x11);
// AMF3 escape
// then write the ByteArray
data.writeObject(pespayload);
tag.push(data, 0, data.length);
_tags.push(tag);
}

/** Parse TS packet. **/
private function _parseTSPacket() : void {
// Each packet is 188 bytes.
Expand Down Expand Up @@ -560,6 +620,41 @@ package org.mangui.hls.demux {
}
}
break;
case _id3Id:
if (_pmtParsed == false) {
break;
}
if (stt) {
if (_curId3PES) {
_parseID3PES(new PES(_curId3PES, false));
}
_curId3PES = new ByteArray();
}
if (_curId3PES) {
// store data. will normally be in a single TS
_curId3PES.writeBytes(_data, _data.position, todo);
var pes : PES = new PES(_curId3PES, false);
if (pes.len && (pes.data.length - pes.payload - pes.payload_len) >= 0) {
CONFIG::LOGGING {
Log.debug2("TS: complete ID3 PES found, parse it");
}
// complete PES, parse and push into the queue
_parseID3PES(pes);
_curId3PES = null;
} else {
CONFIG::LOGGING {
Log.debug("TS: partial ID3 PES");
}
_curId3PES.position = _curId3PES.length;
}
} else {
null;
// just to avoid compilation warnings if CONFIG::LOGGING is false
CONFIG::LOGGING {
Log.warn("TS: Discarding ID3 packet with id " + pid + " bad TS segmentation ?");
}
}
break;
case _avcId:
if (_pmtParsed == false) {
break;
Expand Down Expand Up @@ -652,6 +747,9 @@ package org.mangui.hls.demux {
// ISO/IEC 11172-3 (MPEG-1 audio)
// or ISO/IEC 13818-3 (MPEG-2 halved sample rate audio)
audioList.push(new AudioTrack('TS/MP3 ' + audioList.length, AudioTrack.FROM_DEMUX, sid, (audioList.length == 0)));
} else if (typ == 0x15) {
// ID3 pid
_id3Id = sid;
}
// es_info_length
var sel : uint = _data.readUnsignedShort() & 0xFFF;
Expand Down
7 changes: 7 additions & 0 deletions src/org/mangui/hls/event/HLSEvent.as
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ package org.mangui.hls.event {
public static const PLAYBACK_COMPLETE : String = "hlsEventPlayBackComplete";
/** Identifier for a Playlist Duration updated event **/
public static const PLAYLIST_DURATION_UPDATED : String = "hlsPlayListDurationUpdated";
/** Identifier for a ID3 updated event **/
public static const ID3_UPDATED : String = "hlsID3Updated";

/** The current url **/
public var url : String;
Expand All @@ -76,6 +78,8 @@ package org.mangui.hls.event {
public var state : String;
/** The current audio track **/
public var audioTrack : int;
/** a complete ID3 payload from PES, as a hex dump **/
public var ID3Data:String;

/** Assign event parameter and dispatch. **/
public function HLSEvent(type : String, parameter : *=null) {
Expand Down Expand Up @@ -110,6 +114,9 @@ package org.mangui.hls.event {
case HLSEvent.PLAYLIST_DURATION_UPDATED:
duration = parameter as Number;
break;
case HLSEvent.ID3_UPDATED:
ID3Data = parameter as String;
break;
case HLSEvent.FRAGMENT_PLAYING:
playMetrics = parameter as HLSPlayMetrics;
break;
Expand Down
18 changes: 16 additions & 2 deletions src/org/mangui/hls/stream/FragmentLoader.as
Original file line number Diff line number Diff line change
Expand Up @@ -759,20 +759,33 @@ import org.mangui.hls.event.HLSLoadMetrics;
for each (tag in tags) {
tag.pts = PTS.normalize(ref_pts, tag.pts);
tag.dts = PTS.normalize(ref_pts, tag.dts);
if (tag.type == FLVTag.AAC_HEADER || tag.type == FLVTag.AAC_RAW || tag.type == FLVTag.MP3_RAW) {
switch( tag.type )
{
case FLVTag.AAC_HEADER:
case FLVTag.AAC_RAW:
case FLVTag.MP3_RAW:
fragData.audio_found = true;
fragData.tags_audio_found = true;
fragData.tags_pts_min_audio = Math.min(fragData.tags_pts_min_audio, tag.pts);
fragData.tags_pts_max_audio = Math.max(fragData.tags_pts_max_audio, tag.pts);
fragData.pts_min_audio = Math.min(fragData.pts_min_audio, tag.pts);
fragData.pts_max_audio = Math.max(fragData.pts_max_audio, tag.pts);
} else {
break;

case FLVTag.AVC_HEADER:
case FLVTag.AVC_NALU:
case FLVTag.DISCONTINUITY:
fragData.video_found = true;
fragData.tags_video_found = true;
fragData.tags_pts_min_video = Math.min(fragData.tags_pts_min_video, tag.pts);
fragData.tags_pts_max_video = Math.max(fragData.tags_pts_max_video, tag.pts);
fragData.pts_min_video = Math.min(fragData.pts_min_video, tag.pts);
fragData.pts_max_video = Math.max(fragData.pts_max_video, tag.pts);
break;

case FLVTag.METADATA:
default:
break;
}
fragData.tags.push(tag);
}
Expand Down Expand Up @@ -874,6 +887,7 @@ import org.mangui.hls.event.HLSLoadMetrics;
_hls.dispatchEvent(new HLSEvent(HLSEvent.ERROR, hlsError));
}
if (fragData.audio_found) {
null; // just to stop the compiler warning
CONFIG::LOGGING {
Log.debug("m/M audio PTS:" + fragData.pts_min_audio + "/" + fragData.pts_max_audio);
}
Expand Down
19 changes: 18 additions & 1 deletion src/org/mangui/hls/stream/HLSNetStream.as
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ package org.mangui.hls.stream {
import org.mangui.hls.flv.FLVTag;
import org.mangui.hls.HLS;
import org.mangui.hls.event.HLSEvent;
import org.mangui.hls.utils.Hex;

import flash.events.Event;
import flash.events.NetStatusEvent;
Expand Down Expand Up @@ -86,8 +87,9 @@ package org.mangui.hls.stream {
_seekState = HLSSeekStates.IDLE;
_timer = new Timer(100, 0);
_timer.addEventListener(TimerEvent.TIMER, _checkBuffer);
_client = new HLSNetStreamClient();
_client = new HLSNetStreamClient();
_client.registerCallback("onHLSFragmentChange", onHLSFragmentChange);
_client.registerCallback("onID3Data", onID3Data);
super.client = _client;
};

Expand All @@ -106,6 +108,21 @@ package org.mangui.hls.stream {
_hls.dispatchEvent(new HLSEvent(HLSEvent.FRAGMENT_PLAYING, new HLSPlayMetrics(level, seqnum, cc, audio_only, width, height, tag_list)));
}

// function is called by SCRIPT in FLV
public function onID3Data( data:ByteArray ) : void {
var dump : String = "unset";

// we dump the content as hex to get it to the Javascript in the browser.
// from lots of searching, we could use base64, but even then, the decode would
// not be native, so hex actually seems more efficient
dump = Hex.fromArray(data);

CONFIG::LOGGING {
Log.debug("id3:"+dump);
}
_hls.dispatchEvent(new HLSEvent(HLSEvent.ID3_UPDATED, dump));
}

/** Check the bufferlength. **/
private function _checkBuffer(e : Event) : void {
var playback_absolute_position : Number;
Expand Down

0 comments on commit 2cd5287

Please sign in to comment.