no commit message
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
/* 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.model {
|
||||
/** Audio Track identifier **/
|
||||
public class AudioTrack {
|
||||
public static const FROM_DEMUX : int = 0;
|
||||
public static const FROM_PLAYLIST : int = 1;
|
||||
public var title : String;
|
||||
public var id : int;
|
||||
public var source : int;
|
||||
public var isDefault : Boolean;
|
||||
public var isAAC : Boolean;
|
||||
public var level : Level;
|
||||
|
||||
public function AudioTrack(title : String, source : int, id : int, isDefault : Boolean, isAAC : Boolean) {
|
||||
this.title = title;
|
||||
this.source = source;
|
||||
this.id = id;
|
||||
this.isDefault = isDefault;
|
||||
this.isAAC = isAAC;
|
||||
}
|
||||
|
||||
public function toString() : String {
|
||||
return "AudioTrack ID: " + id + " Title: " + title + " Source: " + source + " Default: " + isDefault + " AAC: " + isAAC;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
/* 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.model {
|
||||
|
||||
import flash.net.ObjectEncoding;
|
||||
import flash.utils.ByteArray;
|
||||
import org.mangui.hls.demux.ID3Tag;
|
||||
import org.mangui.hls.flv.FLVTag;
|
||||
|
||||
/** Fragment model **/
|
||||
public class Fragment {
|
||||
/** Duration of this chunk. **/
|
||||
public var duration : Number;
|
||||
/** Start time of this chunk. **/
|
||||
public var start_time : Number;
|
||||
/** sequence number of this chunk. **/
|
||||
public var seqnum : int;
|
||||
/** URL to this chunk. **/
|
||||
public var url : String;
|
||||
/** level of this chunk. **/
|
||||
public var level : int;
|
||||
/** continuity index of this chunk. **/
|
||||
public var continuity : int;
|
||||
/** program date of this chunk. **/
|
||||
public var program_date : Number;
|
||||
/** URL of the key used to decrypt content **/
|
||||
public var decrypt_url : String;
|
||||
/** Initialization Vector to decrypt content **/
|
||||
public var decrypt_iv : ByteArray;
|
||||
/** byte range start offset **/
|
||||
public var byterange_start_offset : int;
|
||||
/** byte range offset **/
|
||||
public var byterange_end_offset : int;
|
||||
/** data **/
|
||||
public var data : FragmentData;
|
||||
/** custom tags **/
|
||||
public var tag_list : Vector.<String>;
|
||||
|
||||
/** Create the fragment. **/
|
||||
public function Fragment(url : String, duration : Number, level : int, seqnum : int, start_time : Number, continuity : int, program_date : Number, decrypt_url : String, decrypt_iv : ByteArray, byterange_start_offset : int, byterange_end_offset : int, tag_list : Vector.<String>) {
|
||||
this.url = url;
|
||||
this.duration = duration;
|
||||
this.seqnum = seqnum;
|
||||
this.level = level;
|
||||
this.start_time = start_time;
|
||||
this.continuity = continuity;
|
||||
this.program_date = program_date;
|
||||
this.decrypt_url = decrypt_url;
|
||||
this.decrypt_iv = decrypt_iv;
|
||||
this.byterange_start_offset = byterange_start_offset;
|
||||
this.byterange_end_offset = byterange_end_offset;
|
||||
this.tag_list = tag_list;
|
||||
data = new FragmentData();
|
||||
// CONFIG::LOGGING {
|
||||
// Log.info("Frag["+seqnum+"]:duration/start_time,cc="+duration+","+start_time+","+continuity);
|
||||
// }
|
||||
};
|
||||
|
||||
public function getMetadataTag() : FLVTag {
|
||||
var tag : FLVTag = new FLVTag(FLVTag.METADATA, this.data.dts_min, this.data.dts_min, false);
|
||||
var data : ByteArray = new ByteArray();
|
||||
data.objectEncoding = ObjectEncoding.AMF0;
|
||||
data.writeObject("onHLSFragmentChange");
|
||||
data.writeObject(this.level);
|
||||
data.writeObject(this.seqnum);
|
||||
data.writeObject(this.continuity);
|
||||
data.writeObject(this.duration);
|
||||
data.writeObject(!this.data.video_found);
|
||||
data.writeObject(this.program_date);
|
||||
data.writeObject(this.data.video_width);
|
||||
data.writeObject(this.data.video_height);
|
||||
data.writeObject(this.data.auto_level);
|
||||
data.writeObject(this.tag_list.length);
|
||||
this.data.id3_tags ? data.writeObject(this.data.id3_tags.length) : data.writeObject(0);
|
||||
for each (var custom_tag : String in this.tag_list) {
|
||||
data.writeObject(custom_tag);
|
||||
}
|
||||
for each (var id3_tag : ID3Tag in this.data.id3_tags) {
|
||||
data.writeObject(id3_tag.id);
|
||||
data.writeObject(id3_tag.flag);
|
||||
data.writeObject(id3_tag.base64);
|
||||
data.writeObject(id3_tag.data);
|
||||
}
|
||||
tag.push(data, 0, data.length);
|
||||
tag.build();
|
||||
return tag;
|
||||
}
|
||||
|
||||
public function getSkippedTag() : FLVTag {
|
||||
var tag : FLVTag = new FLVTag(FLVTag.METADATA, this.data.pts_start_computed, this.data.pts_start_computed, false);
|
||||
var data : ByteArray = new ByteArray();
|
||||
data.objectEncoding = ObjectEncoding.AMF0;
|
||||
data.writeObject("onHLSFragmentSkipped");
|
||||
data.writeObject(this.level);
|
||||
data.writeObject(this.seqnum);
|
||||
data.writeObject(this.duration);
|
||||
tag.push(data, 0, data.length);
|
||||
tag.build();
|
||||
return tag;
|
||||
}
|
||||
|
||||
public function toString() : String {
|
||||
return "Fragment (seqnum: " + seqnum + ", start_time:" + start_time + ", duration:" + duration + ")";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
/* 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.model {
|
||||
import org.mangui.hls.demux.ID3Tag;
|
||||
import org.mangui.hls.flv.FLVTag;
|
||||
import org.mangui.hls.utils.AES;
|
||||
import org.mangui.hls.utils.PTS;
|
||||
|
||||
import flash.utils.ByteArray;
|
||||
|
||||
/** Fragment Data. **/
|
||||
public class FragmentData {
|
||||
/** valid fragment **/
|
||||
public var valid : Boolean;
|
||||
/** fragment byte array **/
|
||||
public var bytes : ByteArray;
|
||||
/** bytes Loaded **/
|
||||
public var bytesLoaded : int;
|
||||
/** AES decryption instance **/
|
||||
public var decryptAES : AES;
|
||||
/** Start PTS of this chunk. **/
|
||||
public var pts_start : Number;
|
||||
/** computed Start PTS of this chunk. **/
|
||||
public var pts_start_computed : Number;
|
||||
/** min/max audio/video PTS/DTS of this chunk. **/
|
||||
public var pts_min_audio : Number;
|
||||
public var pts_max_audio : Number;
|
||||
public var pts_min_video : Number;
|
||||
public var pts_max_video : Number;
|
||||
public var dts_min : Number;
|
||||
/** audio/video found ? */
|
||||
public var audio_found : Boolean;
|
||||
public var video_found : Boolean;
|
||||
/** tag related stuff */
|
||||
public var metadata_tag_injected : Boolean;
|
||||
private var tags_pts_min_audio : Number;
|
||||
private var tags_pts_max_audio : Number;
|
||||
private var tags_pts_min_video : Number;
|
||||
private var tags_pts_max_video : Number;
|
||||
private var tags_audio_found : Boolean;
|
||||
private var tags_video_found : Boolean;
|
||||
public var tags : Vector.<FLVTag>;
|
||||
/* video dimension */
|
||||
public var video_width : int;
|
||||
public var video_height : int;
|
||||
/* is fragment loaded selected by autolevel algo */
|
||||
public var auto_level : Boolean;
|
||||
/* ID3 tags linked to this fragment */
|
||||
public var id3_tags : Vector.<ID3Tag>;
|
||||
|
||||
/** tag duration */
|
||||
private var audio_tag_duration : Number;
|
||||
private var video_tag_duration : Number;
|
||||
private var audio_tag_last_dts : Number;
|
||||
private var video_tag_last_dts : Number;
|
||||
|
||||
/** Fragment metrics **/
|
||||
public function FragmentData() {
|
||||
this.pts_start = NaN;
|
||||
this.pts_start_computed = NaN;
|
||||
this.valid = true;
|
||||
this.video_width = 0;
|
||||
this.video_height = 0;
|
||||
};
|
||||
|
||||
public function appendTags(tags : Vector.<FLVTag>) : void {
|
||||
// Audio PTS/DTS normalization + min/max computation
|
||||
for each (var tag : FLVTag in tags) {
|
||||
tag.pts = PTS.normalize(pts_start_computed, tag.pts);
|
||||
tag.dts = PTS.normalize(pts_start_computed, tag.dts);
|
||||
dts_min = Math.min(dts_min, tag.dts);
|
||||
switch( tag.type ) {
|
||||
case FLVTag.AAC_RAW:
|
||||
case FLVTag.AAC_HEADER:
|
||||
case FLVTag.MP3_RAW:
|
||||
audio_found = true;
|
||||
tags_audio_found = true;
|
||||
audio_tag_duration = tag.dts - audio_tag_last_dts;
|
||||
audio_tag_last_dts = tag.dts;
|
||||
tags_pts_min_audio = Math.min(tags_pts_min_audio, tag.pts);
|
||||
tags_pts_max_audio = Math.max(tags_pts_max_audio, tag.pts);
|
||||
pts_min_audio = Math.min(pts_min_audio, tag.pts);
|
||||
pts_max_audio = Math.max(pts_max_audio, tag.pts);
|
||||
break;
|
||||
case FLVTag.AVC_HEADER:
|
||||
case FLVTag.AVC_NALU:
|
||||
video_found = true;
|
||||
tags_video_found = true;
|
||||
video_tag_duration = tag.dts - video_tag_last_dts;
|
||||
video_tag_last_dts = tag.dts;
|
||||
tags_pts_min_video = Math.min(tags_pts_min_video, tag.pts);
|
||||
tags_pts_max_video = Math.max(tags_pts_max_video, tag.pts);
|
||||
pts_min_video = Math.min(pts_min_video, tag.pts);
|
||||
pts_max_video = Math.max(pts_max_video, tag.pts);
|
||||
break;
|
||||
case FLVTag.DISCONTINUITY:
|
||||
case FLVTag.METADATA:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
this.tags.push(tag);
|
||||
}
|
||||
}
|
||||
|
||||
public function flushTags() : void {
|
||||
// clean-up tags
|
||||
tags = new Vector.<FLVTag>();
|
||||
tags_audio_found = tags_video_found = false;
|
||||
metadata_tag_injected = false;
|
||||
pts_min_audio = pts_min_video = dts_min = tags_pts_min_audio = tags_pts_min_video = Number.POSITIVE_INFINITY;
|
||||
pts_max_audio = pts_max_video = tags_pts_max_audio = tags_pts_max_video = Number.NEGATIVE_INFINITY;
|
||||
audio_found = video_found = tags_audio_found = tags_video_found = false;
|
||||
}
|
||||
|
||||
public function shiftTags() : void {
|
||||
tags = new Vector.<FLVTag>();
|
||||
if (tags_audio_found) {
|
||||
tags_pts_min_audio = tags_pts_max_audio;
|
||||
tags_audio_found = false;
|
||||
}
|
||||
if (tags_video_found) {
|
||||
tags_pts_min_video = tags_pts_max_video;
|
||||
tags_video_found = false;
|
||||
}
|
||||
}
|
||||
|
||||
public function get pts_min() : Number {
|
||||
if (audio_found) {
|
||||
return pts_min_audio;
|
||||
} else {
|
||||
return pts_min_video;
|
||||
}
|
||||
}
|
||||
|
||||
public function get pts_max() : Number {
|
||||
if (audio_found) {
|
||||
return pts_max_audio;
|
||||
} else {
|
||||
return pts_max_video;
|
||||
}
|
||||
}
|
||||
|
||||
public function get tag_duration() : Number {
|
||||
var duration : Number;
|
||||
if (audio_found) {
|
||||
duration = audio_tag_duration;
|
||||
} else {
|
||||
duration = video_tag_duration;
|
||||
}
|
||||
if(isNaN(duration)) {
|
||||
duration = 0;
|
||||
}
|
||||
return duration;
|
||||
}
|
||||
|
||||
public function get tag_pts_min() : Number {
|
||||
if (audio_found) {
|
||||
return tags_pts_min_audio;
|
||||
} else {
|
||||
return tags_pts_min_video;
|
||||
}
|
||||
}
|
||||
|
||||
public function get tag_pts_max() : Number {
|
||||
if (audio_found) {
|
||||
return tags_pts_max_audio;
|
||||
} else {
|
||||
return tags_pts_max_video;
|
||||
}
|
||||
}
|
||||
|
||||
public function get tag_pts_start_offset() : Number {
|
||||
if (tags_audio_found) {
|
||||
return tags_pts_min_audio - pts_min_audio;
|
||||
} else {
|
||||
return tags_pts_min_video - pts_min_video;
|
||||
}
|
||||
}
|
||||
|
||||
public function get tag_pts_end_offset() : Number {
|
||||
if (tags_audio_found) {
|
||||
return tags_pts_max_audio - pts_min_audio;
|
||||
} else {
|
||||
return tags_pts_max_video - pts_min_video;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,409 @@
|
||||
/* 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.model {
|
||||
CONFIG::LOGGING {
|
||||
import org.mangui.hls.utils.Log;
|
||||
}
|
||||
import org.mangui.hls.utils.PTS;
|
||||
|
||||
/** HLS streaming quality level. **/
|
||||
public class Level {
|
||||
/** audio only Level ? **/
|
||||
public var audio : Boolean;
|
||||
/** AAC codec signaled ? **/
|
||||
public var codec_aac : Boolean;
|
||||
/** MP3 codec signaled ? **/
|
||||
public var codec_mp3 : Boolean;
|
||||
/** H264 codec signaled ? **/
|
||||
public var codec_h264 : Boolean;
|
||||
/** Level Bitrate. **/
|
||||
public var bitrate : uint;
|
||||
/** captions . **/
|
||||
public var closed_captions : String;
|
||||
/** Level Name. **/
|
||||
public var name : String;
|
||||
/** level index (sorted by bitrate) **/
|
||||
public var index : int = 0;
|
||||
/** level index (manifest order) **/
|
||||
public var manifest_index : int = 0;
|
||||
/** video width (from playlist) **/
|
||||
public var width : int;
|
||||
/** video height (from playlist) **/
|
||||
public var height : int;
|
||||
/** URL of this bitrate level (for M3U8). (it is a vector so that we can store redundant streams in same level) **/
|
||||
public var urls : Vector.<String>;
|
||||
// index of used url (non 0 if we switch to a redundant stream)
|
||||
private var _redundantStreamId : int = 0;
|
||||
/** Level fragments **/
|
||||
public var fragments : Vector.<Fragment>;
|
||||
/** min sequence number from M3U8. **/
|
||||
public var start_seqnum : int;
|
||||
/** max sequence number from M3U8. **/
|
||||
public var end_seqnum : int;
|
||||
/** target fragment duration from M3U8 **/
|
||||
public var targetduration : Number;
|
||||
/** average fragment duration **/
|
||||
public var averageduration : Number;
|
||||
/** Total duration **/
|
||||
public var duration : Number;
|
||||
/** Audio Identifier **/
|
||||
public var audio_stream_id : String;
|
||||
|
||||
/** Create the quality level. **/
|
||||
public function Level() : void {
|
||||
this.fragments = new Vector.<Fragment>();
|
||||
};
|
||||
|
||||
public function get url() : String {
|
||||
return urls[_redundantStreamId];
|
||||
}
|
||||
|
||||
public function get redundantStreamsNb() : int {
|
||||
if(urls && urls.length) {
|
||||
return urls.length-1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public function get redundantStreamId() : int {
|
||||
return _redundantStreamId;
|
||||
}
|
||||
|
||||
// when switching to a redundant stream, reset fragments. they will be retrieved from new playlist
|
||||
public function set redundantStreamId(id : int) : void {
|
||||
if(id < urls.length && id != _redundantStreamId) {
|
||||
_redundantStreamId = id;
|
||||
fragments = new Vector.<Fragment>();
|
||||
start_seqnum = end_seqnum = NaN;
|
||||
}
|
||||
}
|
||||
|
||||
/** Return the Fragment before a given time position. **/
|
||||
public function getFragmentBeforePosition(position : Number) : Fragment {
|
||||
if (fragments[0].data.valid && position < fragments[0].start_time)
|
||||
return fragments[0];
|
||||
|
||||
var len : int = fragments.length;
|
||||
for (var i : int = 0; i < len; i++) {
|
||||
/* check whether fragment contains current position */
|
||||
if (fragments[i].data.valid && fragments[i].start_time <= position && fragments[i].start_time + fragments[i].duration > position) {
|
||||
return fragments[i];
|
||||
}
|
||||
}
|
||||
return fragments[len - 1];
|
||||
};
|
||||
|
||||
/** Return the sequence number nearest a given program date **/
|
||||
public function getSeqNumNearestProgramDate(program_date : Number) : int {
|
||||
if (program_date < fragments[0].program_date)
|
||||
return -1;
|
||||
|
||||
var len : int = fragments.length;
|
||||
if(len) {
|
||||
if (program_date > (fragments[len-1].program_date + 1000*fragments[len-1].duration))
|
||||
return -1;
|
||||
|
||||
for (var i : int = 0; i < len; i++) {
|
||||
var frag : Fragment = fragments[i];
|
||||
/* check whether fragment contains current position */
|
||||
if (frag.data.valid &&
|
||||
Math.abs(frag.program_date - program_date) < Math.abs(frag.program_date + 1000 * frag.duration - program_date)) {
|
||||
return frag.seqnum;
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
};
|
||||
|
||||
/** Return the sequence number nearest a PTS **/
|
||||
public function getSeqNumNearestPTS(pts : Number, continuity : int) : Number {
|
||||
if (fragments.length == 0)
|
||||
return -1;
|
||||
var firstIndex : Number = getFirstIndexfromContinuity(continuity);
|
||||
if (firstIndex == -1 || isNaN(fragments[firstIndex].data.pts_start_computed))
|
||||
return -1;
|
||||
var lastIndex : Number = getLastIndexfromContinuity(continuity);
|
||||
|
||||
for (var i : int = firstIndex; i <= lastIndex; i++) {
|
||||
var frag : Fragment = fragments[i];
|
||||
var start : Number = frag.data.pts_start_computed;
|
||||
var duration :Number = frag.duration;
|
||||
var end : Number = start + 1000*duration;
|
||||
|
||||
// CONFIG::LOGGING {
|
||||
// Log.debug("getSeqNumNearestPTS: pts/start/end/duration:" + pts + '/' + start + '/' + end + '/' + duration);
|
||||
// }
|
||||
/* check nearest fragment */
|
||||
if ( frag.data.valid &&
|
||||
(duration >= 0) &&
|
||||
// if PTS is closer from start
|
||||
((Math.abs(start - pts) < Math.abs(end - pts))
|
||||
// start PTS end
|
||||
// *----|-----------------------*
|
||||
//
|
||||
// PTS start end
|
||||
// |--*-----------------------*
|
||||
//
|
||||
//
|
||||
// OR if PTS is bigger than start PTS AND more than 10% before frag end
|
||||
//
|
||||
// start PTS end
|
||||
// *----------------------|-----*
|
||||
// <10%>
|
||||
|| ((pts > start) &&
|
||||
(end - pts ) > 100*duration))) {
|
||||
return frag.seqnum;
|
||||
}
|
||||
}
|
||||
// if we are not at the end of the playlist, then return first sn of next cc range
|
||||
// this is needed to deal with PTS analysis on streams with discontinuity
|
||||
if (lastIndex < end_seqnum) {
|
||||
return frag.seqnum+1;
|
||||
} else {
|
||||
// requested PTS above max PTS of this level
|
||||
return Number.POSITIVE_INFINITY;
|
||||
}
|
||||
};
|
||||
|
||||
public function getLevelstartPTS() : Number {
|
||||
if (fragments.length)
|
||||
return fragments[0].data.pts_start_computed;
|
||||
else
|
||||
return NaN;
|
||||
}
|
||||
|
||||
/** Return the fragment index from fragment sequence number **/
|
||||
public function getFragmentfromSeqNum(seqnum : Number) : Fragment {
|
||||
var index : int = getIndexfromSeqNum(seqnum);
|
||||
if (index != -1) {
|
||||
return fragments[index];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/** Return the fragment index from fragment sequence number **/
|
||||
private function getIndexfromSeqNum(seqnum : int) : int {
|
||||
if (seqnum >= start_seqnum && seqnum <= end_seqnum) {
|
||||
return (fragments.length - 1 - (end_seqnum - seqnum));
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/** Return the first index matching with given continuity counter **/
|
||||
private function getFirstIndexfromContinuity(continuity : int) : int {
|
||||
// look for first fragment matching with given continuity index
|
||||
var len : int = fragments.length;
|
||||
for (var i : int = 0; i < len; i++) {
|
||||
if (fragments[i].continuity == continuity)
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/** Return the first seqnum matching with given continuity counter **/
|
||||
public function getFirstSeqNumfromContinuity(continuity : int) : Number {
|
||||
var index : int = getFirstIndexfromContinuity(continuity);
|
||||
if (index == -1) {
|
||||
return Number.NEGATIVE_INFINITY;
|
||||
}
|
||||
return fragments[index].seqnum;
|
||||
}
|
||||
|
||||
/** Return the last seqnum matching with given continuity counter **/
|
||||
public function getLastSeqNumfromContinuity(continuity : int) : Number {
|
||||
var index : int = getLastIndexfromContinuity(continuity);
|
||||
if (index == -1) {
|
||||
return Number.NEGATIVE_INFINITY;
|
||||
}
|
||||
return fragments[index].seqnum;
|
||||
}
|
||||
|
||||
/** Return the last index matching with given continuity counter **/
|
||||
private function getLastIndexfromContinuity(continuity : Number) : int {
|
||||
var firstIndex : int = getFirstIndexfromContinuity(continuity);
|
||||
if (firstIndex == -1)
|
||||
return -1;
|
||||
|
||||
var lastIndex : int = firstIndex;
|
||||
// look for first fragment matching with given continuity index
|
||||
for (var i : int = firstIndex; i < fragments.length; i++) {
|
||||
if (fragments[i].continuity == continuity)
|
||||
lastIndex = i;
|
||||
else
|
||||
break;
|
||||
}
|
||||
return lastIndex;
|
||||
}
|
||||
|
||||
/** set Fragments **/
|
||||
public function updateFragments(_fragments : Vector.<Fragment>) : void {
|
||||
var idx_with_metrics : int = -1;
|
||||
var len : int = _fragments.length;
|
||||
var continuity_offset : int;
|
||||
var frag : Fragment;
|
||||
// update PTS from previous fragments
|
||||
for (var i : int = 0; i < len; i++) {
|
||||
frag = getFragmentfromSeqNum(_fragments[i].seqnum);
|
||||
if (frag != null) {
|
||||
continuity_offset = frag.continuity - _fragments[i].continuity;
|
||||
if(!isNaN(frag.data.pts_start)) {
|
||||
_fragments[i].data = frag.data;
|
||||
idx_with_metrics = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(continuity_offset) {
|
||||
CONFIG::LOGGING {
|
||||
Log.debug("updateFragments: discontinuity sliding from live playlist,take into account discontinuity drift:" + continuity_offset);
|
||||
}
|
||||
for (i = 0; i < len; i++) {
|
||||
_fragments[i].continuity+= continuity_offset;
|
||||
}
|
||||
}
|
||||
updateFragmentsProgramDate(_fragments);
|
||||
fragments = _fragments;
|
||||
|
||||
if (len > 0) {
|
||||
start_seqnum = _fragments[0].seqnum;
|
||||
end_seqnum = _fragments[len - 1].seqnum;
|
||||
|
||||
if (idx_with_metrics != -1) {
|
||||
frag = fragments[idx_with_metrics];
|
||||
// if at least one fragment contains PTS info, recompute PTS information for all fragments
|
||||
CONFIG::LOGGING {
|
||||
Log.debug("updateFragments: found PTS info from previous playlist,seqnum/PTS:" + frag.seqnum + "/" + frag.data.pts_start);
|
||||
}
|
||||
updateFragment(frag.seqnum, true, frag.data.pts_start, frag.data.pts_start + 1000 * frag.duration);
|
||||
} else {
|
||||
CONFIG::LOGGING {
|
||||
Log.debug("updateFragments: unknown PTS info for this level");
|
||||
}
|
||||
duration = _fragments[len - 1].start_time + _fragments[len - 1].duration;
|
||||
}
|
||||
averageduration = duration / len;
|
||||
} else {
|
||||
duration = 0;
|
||||
averageduration = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private function updateFragmentsProgramDate(_fragments : Vector.<Fragment>) : void {
|
||||
var len : int = _fragments.length;
|
||||
var continuity : int;
|
||||
var program_date : Number;
|
||||
var frag : Fragment;
|
||||
for (var i : int = 0; i < len; i++) {
|
||||
frag = _fragments[i];
|
||||
if (frag.continuity != continuity) {
|
||||
continuity = frag.continuity;
|
||||
program_date = 0;
|
||||
}
|
||||
if (frag.program_date) {
|
||||
program_date = frag.program_date + 1000 * frag.duration;
|
||||
} else if (program_date) {
|
||||
frag.program_date = program_date;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function _updatePTS(from_index : int, to_index : int) : void {
|
||||
// CONFIG::LOGGING {
|
||||
// Log.info("updateFragmentPTS from/to:" + from_index + "/" + to_index);
|
||||
// }
|
||||
var frag_from : Fragment = fragments[from_index];
|
||||
var frag_to : Fragment = fragments[to_index];
|
||||
|
||||
if (frag_from.data.valid && frag_to.data.valid) {
|
||||
if (!isNaN(frag_to.data.pts_start)) {
|
||||
// we know PTS[to_index]
|
||||
frag_to.data.pts_start_computed = frag_to.data.pts_start;
|
||||
/* normalize computed PTS value based on known PTS value.
|
||||
* this is to avoid computing wrong fragment duration in case of PTS looping */
|
||||
var from_pts : Number = PTS.normalize(frag_to.data.pts_start, frag_from.data.pts_start_computed);
|
||||
/* update fragment duration.
|
||||
it helps to fix drifts between playlist reported duration and fragment real duration */
|
||||
if (to_index > from_index) {
|
||||
frag_from.duration = (frag_to.data.pts_start - from_pts) / 1000;
|
||||
CONFIG::LOGGING {
|
||||
if (frag_from.duration < 0) {
|
||||
Log.error("negative duration computed for " + frag_from + ", there should be some duration drift between playlist and fragment!");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
frag_to.duration = ( from_pts - frag_to.data.pts_start) / 1000;
|
||||
CONFIG::LOGGING {
|
||||
if (frag_to.duration < 0) {
|
||||
Log.error("negative duration computed for " + frag_to + ", there should be some duration drift between playlist and fragment!");
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// we dont know PTS[to_index]
|
||||
if (to_index > from_index)
|
||||
frag_to.data.pts_start_computed = frag_from.data.pts_start_computed + 1000 * frag_from.duration;
|
||||
else
|
||||
frag_to.data.pts_start_computed = frag_from.data.pts_start_computed - 1000 * frag_to.duration;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function updateFragment(seqnum : Number, valid : Boolean, min_pts : Number = 0, max_pts : Number = 0) : void {
|
||||
// CONFIG::LOGGING {
|
||||
// Log.info("updatePTS : seqnum/min/max:" + seqnum + '/' + min_pts + '/' + max_pts);
|
||||
// }
|
||||
// get fragment from seqnum
|
||||
var fragIdx : int = getIndexfromSeqNum(seqnum);
|
||||
if (fragIdx != -1) {
|
||||
var frag : Fragment = fragments[fragIdx];
|
||||
// update fragment start PTS + duration
|
||||
if (valid) {
|
||||
frag.data.pts_start = min_pts;
|
||||
frag.data.pts_start_computed = min_pts;
|
||||
frag.duration = (max_pts - min_pts) / 1000;
|
||||
} else {
|
||||
frag.duration = 0;
|
||||
}
|
||||
frag.data.valid = valid;
|
||||
// CONFIG::LOGGING {
|
||||
// Log.info("SN["+fragments[fragIdx].seqnum+"]:pts/duration:" + fragments[fragIdx].start_pts_computed + "/" + fragments[fragIdx].duration);
|
||||
// }
|
||||
|
||||
// adjust fragment PTS/duration from seqnum-1 to frag 0
|
||||
for (var i : int = fragIdx; i > 0 && fragments[i - 1].continuity == frag.continuity; i--) {
|
||||
_updatePTS(i, i - 1);
|
||||
// CONFIG::LOGGING {
|
||||
// Log.info("SN["+fragments[i-1].seqnum+"]:pts/duration:" + fragments[i-1].start_pts_computed + "/" + fragments[i-1].duration);
|
||||
// }
|
||||
}
|
||||
|
||||
// adjust fragment PTS/duration from seqnum to last frag
|
||||
for (i = fragIdx; i < fragments.length - 1 && fragments[i + 1].continuity == frag.continuity; i++) {
|
||||
_updatePTS(i, i + 1);
|
||||
// CONFIG::LOGGING {
|
||||
// Log.info("SN["+fragments[i+1].seqnum+"]:pts/duration:" + fragments[i+1].start_pts_computed + "/" + fragments[i+1].duration);
|
||||
// }
|
||||
}
|
||||
|
||||
// second, adjust fragment offset
|
||||
var start_time_offset : Number = fragments[0].start_time;
|
||||
var len : int = fragments.length;
|
||||
for (i = 0; i < len; i++) {
|
||||
fragments[i].start_time = start_time_offset;
|
||||
start_time_offset += fragments[i].duration;
|
||||
// CONFIG::LOGGING {
|
||||
// Log.info("SN["+fragments[i].seqnum+"]:start_time/continuity/pts/duration:" + fragments[i].start_time + "/" + fragments[i].continuity + "/"+ fragments[i].start_pts_computed + "/" + fragments[i].duration);
|
||||
// }
|
||||
}
|
||||
duration = start_time_offset;
|
||||
} else {
|
||||
CONFIG::LOGGING {
|
||||
Log.error("updateFragment:seqnum " + seqnum + " not found!");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user