mirror of
https://github.com/bytedance/xgplayer.git
synced 2025-04-05 11:18:46 +08:00
233 lines
4.7 KiB
JavaScript
233 lines
4.7 KiB
JavaScript
export class MasterPlaylist {
|
|
version = 0
|
|
streams = [] // MasterStream
|
|
/**
|
|
* @readonly
|
|
*/
|
|
isMaster = true
|
|
}
|
|
|
|
const MediaType = {
|
|
Audio: 'AUDIO',
|
|
Video: 'VIDEO',
|
|
SubTitle: 'SUBTITLE',
|
|
ClosedCaptions: 'CLOSED-CAPTIONS'
|
|
}
|
|
|
|
// #EXT-X-KEY KEYFORMAT values
|
|
// urn:uuid: https://dashif.org/identifiers/content_protection/
|
|
const KeySystems = {
|
|
CLEAR_KEY: 'org.w3.clearkey',
|
|
FAIRPLAY: ['urn:uuid:94ce86fb-07ff-4f43-adb8-93d2fa968ca2', 'com.apple.streamingkeydelivery'],
|
|
WIDEVINE: ['urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed', 'com.widevine.alpha', 'com.widevine'],
|
|
PLAYREADY: ['urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95', 'com.microsoft.playready']
|
|
}
|
|
|
|
function flatArray (arr) {
|
|
let ret = []
|
|
|
|
for (let i = 0; i < arr.length; i++) {
|
|
if (Array.isArray(arr[i])) {
|
|
ret = ret.concat(flatArray(arr[i]))
|
|
} else {
|
|
ret.push(arr[i])
|
|
}
|
|
}
|
|
|
|
return ret
|
|
}
|
|
|
|
export class MediaStream {
|
|
id = 0
|
|
url = ''
|
|
default = false
|
|
autoSelect = false
|
|
forced = false
|
|
group = ''
|
|
name = ''
|
|
lang = ''
|
|
segments = []
|
|
endSN = 0
|
|
}
|
|
|
|
export class AudioStream extends MediaStream {
|
|
mediaType = MediaType.Audio
|
|
channels = 0
|
|
}
|
|
|
|
export class VideoStream extends MediaStream {
|
|
mediaType = MediaType.Video
|
|
}
|
|
|
|
export class SubTitleStream extends MediaStream {
|
|
mediaType = MediaType.SubTitle
|
|
}
|
|
|
|
export class ClosedCaptionsStream extends MediaStream {
|
|
mediaType = MediaType.ClosedCaptions
|
|
}
|
|
|
|
export class MasterStream {
|
|
id = 0
|
|
bitrate = 0
|
|
width = 0
|
|
height = 0
|
|
name = ''
|
|
url = ''
|
|
audioCodec = ''
|
|
videoCodec = ''
|
|
textCodec = ''
|
|
audioGroup = ''
|
|
|
|
/** @type {AudioStream[]} */
|
|
audioStreams = []
|
|
|
|
/** @type {SubTitleStream[]} */
|
|
subtitleStreams = []
|
|
|
|
/** @type {ClosedCaptionsStream[]} */
|
|
closedCaptionsStream = []
|
|
}
|
|
|
|
export class MediaPlaylist {
|
|
version = 0
|
|
url = ''
|
|
type = '' // upper case
|
|
startCC = 0
|
|
endCC = 0
|
|
startSN = 0
|
|
endSN = 0
|
|
totalDuration = 0
|
|
targetDuration = 0
|
|
partTargetDuration = 0
|
|
canSkipUntil = 0
|
|
canSkipDateRanges = false
|
|
skippedSegments = 0
|
|
canBlockReload = false
|
|
partHoldBack = 0
|
|
live = true
|
|
lowLatency = false
|
|
endPartIndex = 0
|
|
/** @type {Array.<MediaSegment>} */
|
|
segments = []
|
|
}
|
|
|
|
export class MediaSegment {
|
|
sn = 0 // Media Sequence Number
|
|
cc = 0
|
|
url = ''
|
|
parentUrl = ''
|
|
title = ''
|
|
start = 0
|
|
duration = 0
|
|
dataTime = ''
|
|
/** @type {?MediaSegmentKey} */
|
|
key = null
|
|
byteRange = null // [start, end]
|
|
isInitSegment = false
|
|
/** @type {?MediaSegment} */
|
|
initSegment = null
|
|
isLast = false
|
|
hasAudio = false
|
|
hasVideo = false
|
|
|
|
independent = false
|
|
partIndex = 0
|
|
|
|
constructor (parentUrl) {
|
|
this.parentUrl = parentUrl
|
|
}
|
|
|
|
get end () {
|
|
return this.start + this.duration
|
|
}
|
|
|
|
setTrackExist (v, a) {
|
|
this.hasVideo = v
|
|
this.hasAudio = a
|
|
}
|
|
|
|
setByteRange (data, prevSegment) {
|
|
this.byteRange = [0]
|
|
const bytes = data.split('@')
|
|
if (bytes.length === 1 && prevSegment && prevSegment.byteRange) {
|
|
this.byteRange[0] = prevSegment.byteRange[1] || 0
|
|
if (this.byteRange[0]) this.byteRange[0] += 1
|
|
} else {
|
|
this.byteRange[0] = parseInt(bytes[1])
|
|
}
|
|
this.byteRange[1] = this.byteRange[0] + parseInt(bytes[0]) - 1
|
|
}
|
|
}
|
|
|
|
export class MediaSegmentKey {
|
|
method = ''
|
|
url = ''
|
|
/** @type {?Uint8Array} */
|
|
iv = null
|
|
keyFormat = ''
|
|
keyFormatVersions = ''
|
|
|
|
constructor (segKey) {
|
|
if (segKey instanceof MediaSegmentKey) {
|
|
this.method = segKey.method
|
|
this.url = segKey.url
|
|
this.keyFormat = segKey.keyFormat
|
|
this.keyFormatVersions = segKey.keyFormatVersions
|
|
if (segKey.iv) this.iv = new Uint8Array(segKey.iv)
|
|
}
|
|
}
|
|
|
|
clone (sn) {
|
|
const key = new MediaSegmentKey(this)
|
|
if (sn !== null && sn !== undefined) key.setIVFromSN(sn)
|
|
return key
|
|
}
|
|
|
|
setIVFromSN (sn) {
|
|
if (!this.iv && this.method === 'AES-128' && typeof sn === 'number' && this.url) {
|
|
this.iv = new Uint8Array(16)
|
|
for (let i = 12; i < 16; i++) {
|
|
this.iv[i] = (sn >> (8 * (15 - i))) & 0xff
|
|
}
|
|
}
|
|
}
|
|
|
|
isSegmentEncrypted () {
|
|
const { method } = this
|
|
return method === 'AES-128' // || method === 'AES-256' || method === 'AES-256-CTR'
|
|
}
|
|
|
|
isValidKeySystem () {
|
|
const isKeyFormatValid =
|
|
flatArray([
|
|
KeySystems.CLEAR_KEY,
|
|
KeySystems.FAIRPLAY,
|
|
KeySystems.WIDEVINE,
|
|
KeySystems.PLAYREADY
|
|
]).indexOf(this.keyFormat) > -1
|
|
if (!isKeyFormatValid) {
|
|
return false
|
|
}
|
|
|
|
const isMethodValid =
|
|
['SAMPLE-AES', 'SAMPLE-AES-CENC', 'SAMPLE-AES-CTR'].indexOf(this.method) > -1
|
|
if (!isMethodValid) {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
isSupported () {
|
|
if (!this.method) {
|
|
return false
|
|
}
|
|
if (this.isSegmentEncrypted()) {
|
|
return true
|
|
} else if (this.isValidKeySystem()) {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
}
|