
11 changed files with 706 additions and 6 deletions
-
154src/m3u8/m3u8-parser.js
-
111src/m3u8/m3u8-tag-types.js
-
17src/m3u8/m3u8.js
-
51src/manifest-controller.js
-
70src/segment-controller.js
-
9test/manifest/brightcove_playlist_m3u8.js
-
57test/manifest/playlist.m3u8
-
57test/manifest/playlistM3U8data.js
-
23test/video-js-hls.html
-
154test/video-js-hls_test.js
-
9video-js-hls.iml
@ -0,0 +1,154 @@ |
|||||
|
(function(window) { |
||||
|
var M3U8 = window.videojs.hls.M3U8; |
||||
|
|
||||
|
window.videojs.hls.M3U8Parser = function() { |
||||
|
|
||||
|
var self = this; |
||||
|
var tagTypes = window.videojs.hls.m3u8TagType; |
||||
|
var lines = []; |
||||
|
var data; |
||||
|
|
||||
|
self.getTagType = function( lineData ) { |
||||
|
for ( var s in tagTypes ) |
||||
|
{ |
||||
|
if (lineData.indexOf(tagTypes[s]) == 0) |
||||
|
{ |
||||
|
return tagTypes[s]; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
self.getTagValue = function ( lineData ) { |
||||
|
for ( var s in tagTypes ) |
||||
|
{ |
||||
|
if (lineData.indexOf(tagTypes[s]) == 0) |
||||
|
{ |
||||
|
return lineData.substr(tagTypes[s].length); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
self.parse = function( rawDataString ) { |
||||
|
data = new M3U8(); |
||||
|
|
||||
|
if( rawDataString != undefined && rawDataString.toString().length > 0 ) |
||||
|
{ |
||||
|
lines = rawDataString.split('\n'); |
||||
|
|
||||
|
lines.forEach( |
||||
|
function(value,index) { |
||||
|
switch( self.getTagType(value) ) |
||||
|
{ |
||||
|
case tagTypes.EXTM3U: |
||||
|
data.hasValidM3UTag = (index == 0); |
||||
|
if(!data.hasValidM3UTag) |
||||
|
{ |
||||
|
data.invalidReasons.push("Invalid EXTM3U Tag"); |
||||
|
} |
||||
|
break; |
||||
|
|
||||
|
case tagTypes.DISCONTINUITY: |
||||
|
break; |
||||
|
|
||||
|
case tagTypes.PLAYLIST_TYPE: |
||||
|
if(self.getTagValue(value) == "VOD" || self.getTagValue(value) == "EVENT") |
||||
|
{ |
||||
|
data.playlistType = self.getTagValue(value); |
||||
|
data.isPlaylist = true; |
||||
|
} else { |
||||
|
data.invalidReasons.push("Invalid Playlist Type Value"); |
||||
|
} |
||||
|
break; |
||||
|
|
||||
|
case tagTypes.EXTINF: |
||||
|
var segment = {url: "unknown", byterange: -1, targetDuration: data.targetDuration }; |
||||
|
|
||||
|
if( self.getTagType(lines[index+1]) == tagTypes.BYTERANGE ) |
||||
|
{ |
||||
|
segment.byterange = self.getTagValue(lines[index+1]).split('@'); |
||||
|
segment.url = lines[index+2]; |
||||
|
} else |
||||
|
{ |
||||
|
segment.url = lines[index+1]; |
||||
|
} |
||||
|
|
||||
|
data.mediaItems.push(segment); |
||||
|
|
||||
|
break; |
||||
|
|
||||
|
case tagTypes.STREAM_INF: |
||||
|
var rendition = {}; |
||||
|
var attributes = value.substr(tagTypes.STREAM_INF.length).split(','); |
||||
|
|
||||
|
attributes.forEach(function(attr_value,attr_index) { |
||||
|
if(isNaN(attr_value.split('=')[1])){ |
||||
|
rendition[attr_value.split('=')[0].toLowerCase()] = attr_value.split('=')[1]; |
||||
|
|
||||
|
if(rendition[attr_value.split('=')[0].toLowerCase()].split('x').length = 2) |
||||
|
{ |
||||
|
rendition.resolution = { |
||||
|
width: Number(rendition[attr_value.split('=')[0].toLowerCase()].split('x')[0]), |
||||
|
height: Number(rendition[attr_value.split('=')[0].toLowerCase()].split('x')[1]) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} else { |
||||
|
rendition[attr_value.split('=')[0].toLowerCase()] = Number(attr_value.split('=')[1]); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
|
||||
|
if( self.getTagType(lines[index+1]) == tagTypes.BYTERANGE ) |
||||
|
{ |
||||
|
rendition.byterange = self.getTagValue(lines[index+1]).split('@'); |
||||
|
rendition.url = lines[index+2]; |
||||
|
} else |
||||
|
{ |
||||
|
rendition.url = lines[index+1]; |
||||
|
} |
||||
|
|
||||
|
data.isPlaylist = true; |
||||
|
data.playlistItems.push(rendition); |
||||
|
break; |
||||
|
|
||||
|
case tagTypes.TARGETDURATION: |
||||
|
data.targetDuration = Number(self.getTagValue(value).split(',')[0]); |
||||
|
break; |
||||
|
|
||||
|
case tagTypes.ZEN_TOTAL_DURATION: |
||||
|
data.totalDuration = self.getTagValue(value); |
||||
|
break; |
||||
|
|
||||
|
case tagTypes.VERSION: |
||||
|
data.version = Number(self.getTagValue(value)); |
||||
|
break; |
||||
|
|
||||
|
case tagTypes.MEDIA_SEQUENCE: |
||||
|
data.mediaSequence = parseInt(self.getTagValue(value)); |
||||
|
break; |
||||
|
|
||||
|
case tagTypes.ALLOW_CACHE: |
||||
|
if(self.getTagValue(value) == "YES" || self.getTagValue(value) == "NO") |
||||
|
{ |
||||
|
data.allowCache = self.getTagValue(value); |
||||
|
} else { |
||||
|
data.invalidReasons.push("Invalid ALLOW_CACHE Value"); |
||||
|
} |
||||
|
break; |
||||
|
|
||||
|
case tagTypes.ENDLIST: |
||||
|
data.hasEndTag = true; |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
) |
||||
|
} else { |
||||
|
data.invalidReasons.push("Empty Manifest"); |
||||
|
} |
||||
|
|
||||
|
return data; |
||||
|
|
||||
|
}; |
||||
|
}; |
||||
|
|
||||
|
})(this); |
@ -0,0 +1,111 @@ |
|||||
|
window.videojs.hls.m3u8TagType = { |
||||
|
/* |
||||
|
* Derived from V8: http://tools.ietf.org/html/draft-pantos-http-live-streaming-08
|
||||
|
*/ |
||||
|
|
||||
|
/** |
||||
|
* Identifies manifest as Extended M3U - must be present on first line! |
||||
|
*/ |
||||
|
EXTM3U:"#EXTM3U", |
||||
|
|
||||
|
/** |
||||
|
* Specifies duration. |
||||
|
* Syntax: #EXTINF:<duration>,<title> |
||||
|
* Example: #EXTINF:10, |
||||
|
*/ |
||||
|
EXTINF:"#EXTINF:", |
||||
|
|
||||
|
/** |
||||
|
* Indicates that a media segment is a sub-range of the resource identified by its media URI. |
||||
|
* Syntax: #EXT-X-BYTERANGE:<n>[@o] |
||||
|
*/ |
||||
|
BYTERANGE:"#EXT-X-BYTERANGE:", |
||||
|
|
||||
|
/** |
||||
|
* Specifies the maximum media segment duration - applies to entire manifest. |
||||
|
* Syntax: #EXT-X-TARGETDURATION:<s> |
||||
|
* Example: #EXT-X-TARGETDURATION:10 |
||||
|
*/ |
||||
|
TARGETDURATION:"#EXT-X-TARGETDURATION:", |
||||
|
|
||||
|
/** |
||||
|
* Specifies the sequence number of the first URI in a manifest. |
||||
|
* Syntax: #EXT-X-MEDIA-SEQUENCE:<i> |
||||
|
* Example: #EXT-X-MEDIA-SEQUENCE:50 |
||||
|
*/ |
||||
|
MEDIA_SEQUENCE:"#EXT-X-MEDIA-SEQUENCE:", |
||||
|
|
||||
|
/** |
||||
|
* Specifies a method by which media segments can be decrypted, if encryption is present. |
||||
|
* Syntax: #EXT-X-KEY:<attribute-list> |
||||
|
* Note: This is likely irrelevant in the context of the Flash Player. |
||||
|
*/ |
||||
|
KEY:"#EXT-X-KEY:", |
||||
|
|
||||
|
/** |
||||
|
* Associates the first sample of a media segment with an absolute date and/or time. Applies only to the next media URI. |
||||
|
* Syntax: #EXT-X-PROGRAM-DATE-TIME:<YYYY-MM-DDThh:mm:ssZ> |
||||
|
* Example: #EXT-X-PROGRAM-DATE-TIME:2010-02-19T14:54:23.031+08:00 |
||||
|
*/ |
||||
|
PROGRAM_DATE_TIME:"#EXT-X-PROGRAM-DATE-TIME:", |
||||
|
|
||||
|
/** |
||||
|
* Indicates whether the client MAY or MUST NOT cache downloaded media segments for later replay. |
||||
|
* Syntax: #EXT-X-ALLOW-CACHE:<YES|NO> |
||||
|
* Note: This is likely irrelevant in the context of the Flash Player. |
||||
|
*/ |
||||
|
ALLOW_CACHE:"#EXT-X-ALLOW_CACHE:", |
||||
|
|
||||
|
/** |
||||
|
* Provides mutability information about the manifest. |
||||
|
* Syntax: #EXT-X-PLAYLIST-TYPE:<EVENT|VOD> |
||||
|
*/ |
||||
|
PLAYLIST_TYPE:"#EXT-X-PLAYLIST-TYPE:", |
||||
|
|
||||
|
/** |
||||
|
* Indicates that no more media segments will be added to the manifest. May occur ONCE, anywhere in the mainfest file. |
||||
|
*/ |
||||
|
ENDLIST:"#EXT-X-ENDLIST", |
||||
|
|
||||
|
/** |
||||
|
* Used to relate Playlists that contain alternative renditions of the same content. |
||||
|
* Syntax: #EXT-X-MEDIA:<attribute-list> |
||||
|
*/ |
||||
|
MEDIA:"#EXT-X-MEDIA:", |
||||
|
|
||||
|
/** |
||||
|
* Identifies a media URI as a Playlist file containing a multimedia presentation and provides information about that presentation. |
||||
|
* Syntax: #EXT-X-STREAM-INF:<attribute-list> |
||||
|
* <URI> |
||||
|
*/ |
||||
|
STREAM_INF:"#EXT-X-STREAM-INF:", |
||||
|
|
||||
|
/** |
||||
|
* Indicates an encoding discontinuity between the media segment that follows it and the one that preceded it. |
||||
|
*/ |
||||
|
DISCONTINUITY:"#EXT-X-DISCONTINUITY", |
||||
|
|
||||
|
/** |
||||
|
* Indicates that each media segment in the manifest describes a single I-frame. |
||||
|
*/ |
||||
|
I_FRAMES_ONLY:"#EXT-X-I-FRAMES-ONLY", |
||||
|
|
||||
|
/** |
||||
|
* Identifies a manifest file containing the I-frames of a multimedia presentation. It stands alone, in that it does not apply to a particular URI in the manifest. |
||||
|
* Syntax: #EXT-X-I-FRAME-STREAM-INF:<attribute-list> |
||||
|
*/ |
||||
|
I_FRAME_STREAM_INF:"#EXT-X-I-FRAME-STREAM-INF:", |
||||
|
|
||||
|
/** |
||||
|
* Indicates the compatibility version of the Playlist file. |
||||
|
* Syntax: #EXT-X-VERSION:<n> |
||||
|
*/ |
||||
|
VERSION:"#EXT-X-VERSION:", |
||||
|
|
||||
|
/** |
||||
|
* Indicates the total duration as reported by Zencoder. |
||||
|
* Syntax: #ZEN-TOTAL-DURATION:<n> |
||||
|
*/ |
||||
|
ZEN_TOTAL_DURATION: "#ZEN-TOTAL-DURATION:" |
||||
|
|
||||
|
}; |
@ -0,0 +1,17 @@ |
|||||
|
(function(window) { |
||||
|
window.videojs.hls.M3U8 = function() { |
||||
|
this.allowCache = "NO"; |
||||
|
this.playlistItems = []; |
||||
|
this.mediaItems = []; |
||||
|
this.iFrameItems = []; |
||||
|
this.invalidReasons = []; |
||||
|
this.hasValidM3UTag = false; |
||||
|
this.hasEndTag = false; |
||||
|
this.targetDuration = -1; |
||||
|
this.totalDuration = -1; |
||||
|
this.isPlaylist = false; |
||||
|
this.playlistType = ""; |
||||
|
this.mediaSequence = -1; |
||||
|
this.version = -1; |
||||
|
} |
||||
|
})(this); |
@ -0,0 +1,51 @@ |
|||||
|
(function(window) { |
||||
|
var M3U8 = window.videojs.hls.M3U8; |
||||
|
var M3U8Parser = window.videojs.hls.M3U8Parser; |
||||
|
|
||||
|
window.videojs.hls.ManifestController = function(){ |
||||
|
|
||||
|
var self = this; |
||||
|
var parser; |
||||
|
var data; |
||||
|
|
||||
|
var onDataCallback; |
||||
|
var onErrorCallback; |
||||
|
var onUpdateCallback; |
||||
|
|
||||
|
self.loadManifest = function ( manifestUrl, onDataCallback, onErrorCallback, onUpdateCallback ) { |
||||
|
self.onDataCallback = onDataCallback; |
||||
|
self.onErrorCallback = onErrorCallback; |
||||
|
self.onUpdateCallback = onUpdateCallback; |
||||
|
|
||||
|
vjs.get(manifestUrl, self.onManifestLoadComplete, self.onManifestLoadError); |
||||
|
}; |
||||
|
|
||||
|
self.parseManifest = function ( dataAsString ) { |
||||
|
self.parser = new M3U8Parser(); |
||||
|
self.data = self.parser.parse( dataAsString ); |
||||
|
|
||||
|
return self.data; |
||||
|
}; |
||||
|
|
||||
|
self.onManifestLoadComplete = function(response) { |
||||
|
var output = self.parseManifest(response); |
||||
|
|
||||
|
if(self.onDataCallback != undefined) |
||||
|
{ |
||||
|
self.onDataCallback(output); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
self.onManifestLoadError = function(err) { |
||||
|
if(err) |
||||
|
{ |
||||
|
console.log(err.message); |
||||
|
} |
||||
|
|
||||
|
if(self.onErrorCallback != undefined) |
||||
|
{ |
||||
|
onErrorCallback((err != undefined) ? err : null); |
||||
|
} |
||||
|
}; |
||||
|
} |
||||
|
})(this); |
@ -0,0 +1,70 @@ |
|||||
|
(function(window) { |
||||
|
|
||||
|
var SegmentParser = window.videojs.hls.SegmentParser; |
||||
|
|
||||
|
window.videojs.hls.SegmentController = function(){ |
||||
|
|
||||
|
var self = this; |
||||
|
var url; |
||||
|
var parser; |
||||
|
var requestTimestamp; |
||||
|
var responseTimestamp; |
||||
|
var data; |
||||
|
|
||||
|
var onDataCallback; |
||||
|
var onErrorCallback; |
||||
|
var onUpdateCallback; |
||||
|
|
||||
|
self.loadSegment = function ( segmentUrl, onDataCallback, onErrorCallback, onUpdateCallback ) { |
||||
|
self.url = segmentUrl; |
||||
|
self.onDataCallback = onDataCallback; |
||||
|
self.onErrorCallback = onErrorCallback; |
||||
|
self.onUpdateCallback = onUpdateCallback; |
||||
|
self.requestTimestamp = new Date().getTime(); |
||||
|
|
||||
|
vjs.get(segmentUrl, self.onSegmentLoadComplete, self.onSegmentLoadError); |
||||
|
}; |
||||
|
|
||||
|
self.parseSegment = function ( incomingData ) { |
||||
|
// Add David's code later //
|
||||
|
|
||||
|
self.data = {}; |
||||
|
self.data.url = self.url; |
||||
|
self.data.isCached = false; |
||||
|
self.data.requestTimestamp = self.requestTimestamp; |
||||
|
self.data.responseTimestamp = self.responseTimestamp; |
||||
|
self.data.byteLength = incomingData.byteLength; |
||||
|
self.data.isCached = ( parseInt(self.responseTimestamp - self.requestTimestamp) < 75 ); |
||||
|
self.data.throughput = self.calculateThroughput(self.data.byteLength, self.requestTimestamp ,self.responseTimestamp) |
||||
|
|
||||
|
return self.data; |
||||
|
}; |
||||
|
|
||||
|
self.calculateThroughput = function(dataAmount, startTime, endTime) { |
||||
|
return Math.round(dataAmount/(endTime-startTime)*1000)*8; |
||||
|
} |
||||
|
|
||||
|
self.onSegmentLoadComplete = function(response) { |
||||
|
self.responseTimestamp = new Date().getTime(); |
||||
|
|
||||
|
var output = self.parseSegment(response); |
||||
|
|
||||
|
if(self.onDataCallback != undefined) |
||||
|
{ |
||||
|
self.onDataCallback(output); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
self.onSegmentLoadError = function(err) { |
||||
|
if(err) |
||||
|
{ |
||||
|
console.log(err.message); |
||||
|
} |
||||
|
|
||||
|
if(self.onErrorCallback != undefined) |
||||
|
{ |
||||
|
onErrorCallback((err != undefined) ? err : null); |
||||
|
} |
||||
|
}; |
||||
|
} |
||||
|
})(this); |
@ -0,0 +1,9 @@ |
|||||
|
window.brightcove_playlist_data = '#EXTM3U\n'+ |
||||
|
'#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=240000,RESOLUTION=396x224\n'+ |
||||
|
'http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824686811001&videoId=1824650741001\n'+ |
||||
|
'#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=40000\n'+ |
||||
|
'http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824683759001&videoId=1824650741001\n'+ |
||||
|
'#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=440000,RESOLUTION=396x224\n'+ |
||||
|
'http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824686593001&videoId=1824650741001\n'+ |
||||
|
'#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=1928000,RESOLUTION=960x540\n'+ |
||||
|
'http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824687660001&videoId=1824650741001' |
@ -0,0 +1,57 @@ |
|||||
|
#EXTM3U |
||||
|
#EXT-X-TARGETDURATION:10 |
||||
|
#EXT-X-VERSION:4 |
||||
|
#EXT-X-MEDIA-SEQUENCE:0 |
||||
|
#EXT-X-PLAYLIST-TYPE:VOD |
||||
|
#EXTINF:10, |
||||
|
#EXT-X-BYTERANGE:522828@0 |
||||
|
hls_450k_video.ts |
||||
|
#EXTINF:10, |
||||
|
#EXT-X-BYTERANGE:587500@522828 |
||||
|
hls_450k_video.ts |
||||
|
#EXTINF:10, |
||||
|
#EXT-X-BYTERANGE:713084@1110328 |
||||
|
hls_450k_video.ts |
||||
|
#EXTINF:10, |
||||
|
#EXT-X-BYTERANGE:476580@1823412 |
||||
|
hls_450k_video.ts |
||||
|
#EXTINF:10, |
||||
|
#EXT-X-BYTERANGE:535612@2299992 |
||||
|
hls_450k_video.ts |
||||
|
#EXTINF:10, |
||||
|
#EXT-X-BYTERANGE:207176@2835604 |
||||
|
hls_450k_video.ts |
||||
|
#EXTINF:10, |
||||
|
#EXT-X-BYTERANGE:455900@3042780 |
||||
|
hls_450k_video.ts |
||||
|
#EXTINF:10, |
||||
|
#EXT-X-BYTERANGE:657248@3498680 |
||||
|
hls_450k_video.ts |
||||
|
#EXTINF:10, |
||||
|
#EXT-X-BYTERANGE:571708@4155928 |
||||
|
hls_450k_video.ts |
||||
|
#EXTINF:10, |
||||
|
#EXT-X-BYTERANGE:485040@4727636 |
||||
|
hls_450k_video.ts |
||||
|
#EXTINF:10, |
||||
|
#EXT-X-BYTERANGE:709136@5212676 |
||||
|
hls_450k_video.ts |
||||
|
#EXTINF:10, |
||||
|
#EXT-X-BYTERANGE:730004@5921812 |
||||
|
hls_450k_video.ts |
||||
|
#EXTINF:10, |
||||
|
#EXT-X-BYTERANGE:456276@6651816 |
||||
|
hls_450k_video.ts |
||||
|
#EXTINF:10, |
||||
|
#EXT-X-BYTERANGE:468684@7108092 |
||||
|
hls_450k_video.ts |
||||
|
#EXTINF:10, |
||||
|
#EXT-X-BYTERANGE:444996@7576776 |
||||
|
hls_450k_video.ts |
||||
|
#EXTINF:10, |
||||
|
#EXT-X-BYTERANGE:331444@8021772 |
||||
|
hls_450k_video.ts |
||||
|
#EXTINF:1.4167, |
||||
|
#EXT-X-BYTERANGE:44556@8353216 |
||||
|
hls_450k_video.ts |
||||
|
#EXT-X-ENDLIST |
@ -0,0 +1,57 @@ |
|||||
|
window.playlistData = '#EXTM3U\n'+ |
||||
|
'#EXT-X-TARGETDURATION:10\n' + |
||||
|
'#EXT-X-VERSION:4\n' + |
||||
|
'#EXT-X-MEDIA-SEQUENCE:0\n' + |
||||
|
'#EXT-X-PLAYLIST-TYPE:VOD\n' + |
||||
|
'#EXTINF:10,\n' + |
||||
|
'#EXT-X-BYTERANGE:522828@0\n' + |
||||
|
'hls_450k_video.ts\n' + |
||||
|
'#EXTINF:10,\n' + |
||||
|
'#EXT-X-BYTERANGE:587500@522828\n' + |
||||
|
'hls_450k_video.ts\n' + |
||||
|
'#EXTINF:10,\n' + |
||||
|
'#EXT-X-BYTERANGE:713084@1110328\n' + |
||||
|
'hls_450k_video.ts\n' + |
||||
|
'#EXTINF:10,\n' + |
||||
|
'#EXT-X-BYTERANGE:476580@1823412\n' + |
||||
|
'hls_450k_video.ts\n' + |
||||
|
'#EXTINF:10,\n' + |
||||
|
'#EXT-X-BYTERANGE:535612@2299992\n' + |
||||
|
'hls_450k_video.ts\n' + |
||||
|
'#EXTINF:10,\n' + |
||||
|
'#EXT-X-BYTERANGE:207176@2835604\n' + |
||||
|
'hls_450k_video.ts\n' + |
||||
|
'#EXTINF:10,\n' + |
||||
|
'#EXT-X-BYTERANGE:455900@3042780\n' + |
||||
|
'hls_450k_video.ts\n' + |
||||
|
'#EXTINF:10,\n' + |
||||
|
'#EXT-X-BYTERANGE:657248@3498680\n' + |
||||
|
'hls_450k_video.ts\n' + |
||||
|
'#EXTINF:10,\n' + |
||||
|
'#EXT-X-BYTERANGE:571708@4155928\n' + |
||||
|
'hls_450k_video.ts\n' + |
||||
|
'#EXTINF:10,\n' + |
||||
|
'#EXT-X-BYTERANGE:485040@4727636\n' + |
||||
|
'hls_450k_video.ts\n' + |
||||
|
'#EXTINF:10,\n' + |
||||
|
'#EXT-X-BYTERANGE:709136@5212676\n' + |
||||
|
'hls_450k_video.ts\n' + |
||||
|
'#EXTINF:10,\n' + |
||||
|
'#EXT-X-BYTERANGE:730004@5921812\n' + |
||||
|
'hls_450k_video.ts\n' + |
||||
|
'#EXTINF:10,\n' + |
||||
|
'#EXT-X-BYTERANGE:456276@6651816\n' + |
||||
|
'hls_450k_video.ts\n' + |
||||
|
'#EXTINF:10,\n' + |
||||
|
'#EXT-X-BYTERANGE:468684@7108092\n' + |
||||
|
'hls_450k_video.ts' + |
||||
|
'#EXTINF:10,\n' + |
||||
|
'#EXT-X-BYTERANGE:444996@7576776\n' + |
||||
|
'hls_450k_video.ts\n' + |
||||
|
'#EXTINF:10,\n' + |
||||
|
'#EXT-X-BYTERANGE:331444@8021772\n' + |
||||
|
'hls_450k_video.ts\n' + |
||||
|
'#EXTINF:1.4167,\n' + |
||||
|
'#EXT-X-BYTERANGE:44556@8353216\n' + |
||||
|
'hls_450k_video.ts\n' + |
||||
|
'#EXT-X-ENDLIST'; |
@ -0,0 +1,9 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||
|
<module type="WEB_MODULE" version="4"> |
||||
|
<component name="NewModuleRootManager" inherit-compiler-output="true"> |
||||
|
<exclude-output /> |
||||
|
<content url="file://$MODULE_DIR$" /> |
||||
|
<orderEntry type="sourceFolder" forTests="false" /> |
||||
|
</component> |
||||
|
</module> |
||||
|
|
Write
Preview
Loading…
Cancel
Save
Reference in new issue