Introduce streaming playlists command
This commit is contained in:
parent
6910f20f11
commit
57f879a540
|
@ -15,7 +15,6 @@ import {
|
||||||
doubleFollow,
|
doubleFollow,
|
||||||
flushAndRunMultipleServers,
|
flushAndRunMultipleServers,
|
||||||
getMyVideosWithFilter,
|
getMyVideosWithFilter,
|
||||||
getPlaylist,
|
|
||||||
getVideo,
|
getVideo,
|
||||||
getVideosList,
|
getVideosList,
|
||||||
getVideosWithFilters,
|
getVideosWithFilters,
|
||||||
|
@ -397,20 +396,27 @@ describe('Test live', function () {
|
||||||
// Only finite files are displayed
|
// Only finite files are displayed
|
||||||
expect(hlsPlaylist.files).to.have.lengthOf(0)
|
expect(hlsPlaylist.files).to.have.lengthOf(0)
|
||||||
|
|
||||||
await checkResolutionsInMasterPlaylist(hlsPlaylist.playlistUrl, resolutions)
|
await checkResolutionsInMasterPlaylist({ server, playlistUrl: hlsPlaylist.playlistUrl, resolutions })
|
||||||
|
|
||||||
for (let i = 0; i < resolutions.length; i++) {
|
for (let i = 0; i < resolutions.length; i++) {
|
||||||
const segmentNum = 3
|
const segmentNum = 3
|
||||||
const segmentName = `${i}-00000${segmentNum}.ts`
|
const segmentName = `${i}-00000${segmentNum}.ts`
|
||||||
await commands[0].waitUntilSegmentGeneration({ videoUUID: video.uuid, resolution: i, segment: segmentNum })
|
await commands[0].waitUntilSegmentGeneration({ videoUUID: video.uuid, resolution: i, segment: segmentNum })
|
||||||
|
|
||||||
const res = await getPlaylist(`${servers[0].url}/static/streaming-playlists/hls/${video.uuid}/${i}.m3u8`)
|
const subPlaylist = await servers[0].streamingPlaylistsCommand.get({
|
||||||
const subPlaylist = res.text
|
url: `${servers[0].url}/static/streaming-playlists/hls/${video.uuid}/${i}.m3u8`
|
||||||
|
})
|
||||||
|
|
||||||
expect(subPlaylist).to.contain(segmentName)
|
expect(subPlaylist).to.contain(segmentName)
|
||||||
|
|
||||||
const baseUrlAndPath = servers[0].url + '/static/streaming-playlists/hls'
|
const baseUrlAndPath = servers[0].url + '/static/streaming-playlists/hls'
|
||||||
await checkLiveSegmentHash(baseUrlAndPath, video.uuid, segmentName, hlsPlaylist)
|
await checkLiveSegmentHash({
|
||||||
|
server,
|
||||||
|
baseUrlSegment: baseUrlAndPath,
|
||||||
|
videoUUID: video.uuid,
|
||||||
|
segmentName,
|
||||||
|
hlsPlaylist
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -203,7 +203,7 @@ async function check1PlaylistRedundancies (videoUUID?: string) {
|
||||||
const hlsPlaylist = (res.body as VideoDetails).streamingPlaylists[0]
|
const hlsPlaylist = (res.body as VideoDetails).streamingPlaylists[0]
|
||||||
|
|
||||||
for (const resolution of [ 240, 360, 480, 720 ]) {
|
for (const resolution of [ 240, 360, 480, 720 ]) {
|
||||||
await checkSegmentHash(baseUrlPlaylist, baseUrlSegment, videoUUID, resolution, hlsPlaylist)
|
await checkSegmentHash({ server: servers[1], baseUrlPlaylist, baseUrlSegment, videoUUID, resolution, hlsPlaylist })
|
||||||
}
|
}
|
||||||
|
|
||||||
const directories = [
|
const directories = [
|
||||||
|
|
|
@ -12,7 +12,6 @@ import {
|
||||||
cleanupTests,
|
cleanupTests,
|
||||||
doubleFollow,
|
doubleFollow,
|
||||||
flushAndRunMultipleServers,
|
flushAndRunMultipleServers,
|
||||||
getPlaylist,
|
|
||||||
getVideo,
|
getVideo,
|
||||||
makeRawRequest,
|
makeRawRequest,
|
||||||
removeVideo,
|
removeVideo,
|
||||||
|
@ -67,10 +66,9 @@ async function checkHlsPlaylist (servers: ServerInfo[], videoUUID: string, hlsOn
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
await checkResolutionsInMasterPlaylist(hlsPlaylist.playlistUrl, resolutions)
|
await checkResolutionsInMasterPlaylist({ server, playlistUrl: hlsPlaylist.playlistUrl, resolutions })
|
||||||
|
|
||||||
const res = await getPlaylist(hlsPlaylist.playlistUrl)
|
const masterPlaylist = await server.streamingPlaylistsCommand.get({ url: hlsPlaylist.playlistUrl })
|
||||||
const masterPlaylist = res.text
|
|
||||||
|
|
||||||
for (const resolution of resolutions) {
|
for (const resolution of resolutions) {
|
||||||
expect(masterPlaylist).to.contain(`${resolution}.m3u8`)
|
expect(masterPlaylist).to.contain(`${resolution}.m3u8`)
|
||||||
|
@ -80,9 +78,10 @@ async function checkHlsPlaylist (servers: ServerInfo[], videoUUID: string, hlsOn
|
||||||
|
|
||||||
{
|
{
|
||||||
for (const resolution of resolutions) {
|
for (const resolution of resolutions) {
|
||||||
const res = await getPlaylist(`${baseUrl}/static/streaming-playlists/hls/${videoUUID}/${resolution}.m3u8`)
|
const subPlaylist = await server.streamingPlaylistsCommand.get({
|
||||||
|
url: `${baseUrl}/static/streaming-playlists/hls/${videoUUID}/${resolution}.m3u8`
|
||||||
|
})
|
||||||
|
|
||||||
const subPlaylist = res.text
|
|
||||||
expect(subPlaylist).to.contain(`${videoUUID}-${resolution}-fragmented.mp4`)
|
expect(subPlaylist).to.contain(`${videoUUID}-${resolution}-fragmented.mp4`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -91,7 +90,14 @@ async function checkHlsPlaylist (servers: ServerInfo[], videoUUID: string, hlsOn
|
||||||
const baseUrlAndPath = baseUrl + '/static/streaming-playlists/hls'
|
const baseUrlAndPath = baseUrl + '/static/streaming-playlists/hls'
|
||||||
|
|
||||||
for (const resolution of resolutions) {
|
for (const resolution of resolutions) {
|
||||||
await checkSegmentHash(baseUrlAndPath, baseUrlAndPath, videoUUID, resolution, hlsPlaylist)
|
await checkSegmentHash({
|
||||||
|
server,
|
||||||
|
baseUrlPlaylist: baseUrlAndPath,
|
||||||
|
baseUrlSegment: baseUrlAndPath,
|
||||||
|
videoUUID,
|
||||||
|
resolution,
|
||||||
|
hlsPlaylist
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,8 @@ import {
|
||||||
ImportsCommand,
|
ImportsCommand,
|
||||||
LiveCommand,
|
LiveCommand,
|
||||||
PlaylistsCommand,
|
PlaylistsCommand,
|
||||||
ServicesCommand
|
ServicesCommand,
|
||||||
|
StreamingPlaylistsCommand
|
||||||
} from '../videos'
|
} from '../videos'
|
||||||
import { ConfigCommand } from './config-command'
|
import { ConfigCommand } from './config-command'
|
||||||
import { ContactFormCommand } from './contact-form-command'
|
import { ContactFormCommand } from './contact-form-command'
|
||||||
|
@ -117,6 +118,7 @@ interface ServerInfo {
|
||||||
playlistsCommand?: PlaylistsCommand
|
playlistsCommand?: PlaylistsCommand
|
||||||
historyCommand?: HistoryCommand
|
historyCommand?: HistoryCommand
|
||||||
importsCommand?: ImportsCommand
|
importsCommand?: ImportsCommand
|
||||||
|
streamingPlaylistsCommand?: StreamingPlaylistsCommand
|
||||||
}
|
}
|
||||||
|
|
||||||
function parallelTests () {
|
function parallelTests () {
|
||||||
|
@ -350,6 +352,7 @@ async function runServer (server: ServerInfo, configOverrideArg?: any, args = []
|
||||||
server.playlistsCommand = new PlaylistsCommand(server)
|
server.playlistsCommand = new PlaylistsCommand(server)
|
||||||
server.historyCommand = new HistoryCommand(server)
|
server.historyCommand = new HistoryCommand(server)
|
||||||
server.importsCommand = new ImportsCommand(server)
|
server.importsCommand = new ImportsCommand(server)
|
||||||
|
server.streamingPlaylistsCommand = new StreamingPlaylistsCommand(server)
|
||||||
|
|
||||||
res(server)
|
res(server)
|
||||||
})
|
})
|
||||||
|
|
|
@ -16,6 +16,9 @@ export interface OverrideCommandOptions {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface InternalCommonCommandOptions extends OverrideCommandOptions {
|
interface InternalCommonCommandOptions extends OverrideCommandOptions {
|
||||||
|
// Default to server.url
|
||||||
|
url?: string
|
||||||
|
|
||||||
path: string
|
path: string
|
||||||
// If we automatically send the server token if the token is not provided
|
// If we automatically send the server token if the token is not provided
|
||||||
implicitToken: boolean
|
implicitToken: boolean
|
||||||
|
@ -27,6 +30,7 @@ interface InternalGetCommandOptions extends InternalCommonCommandOptions {
|
||||||
contentType?: string
|
contentType?: string
|
||||||
accept?: string
|
accept?: string
|
||||||
redirects?: number
|
redirects?: number
|
||||||
|
range?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class AbstractCommand {
|
abstract class AbstractCommand {
|
||||||
|
@ -55,6 +59,22 @@ abstract class AbstractCommand {
|
||||||
return unwrapText(this.getRequest(options))
|
return unwrapText(this.getRequest(options))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected getRawRequest (options: Omit<InternalGetCommandOptions, 'path'>) {
|
||||||
|
const { url, range } = options
|
||||||
|
const { host, protocol, pathname } = new URL(url)
|
||||||
|
|
||||||
|
return this.getRequest({
|
||||||
|
...options,
|
||||||
|
|
||||||
|
token: this.buildCommonRequestToken(options),
|
||||||
|
defaultExpectedStatus: this.buildStatusCodeExpected(options),
|
||||||
|
|
||||||
|
url: `${protocol}//${host}`,
|
||||||
|
path: pathname,
|
||||||
|
range
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
protected getRequest (options: InternalGetCommandOptions) {
|
protected getRequest (options: InternalGetCommandOptions) {
|
||||||
const { redirects, query, contentType, accept } = options
|
const { redirects, query, contentType, accept } = options
|
||||||
|
|
||||||
|
@ -127,21 +147,32 @@ abstract class AbstractCommand {
|
||||||
}
|
}
|
||||||
|
|
||||||
private buildCommonRequestOptions (options: InternalCommonCommandOptions) {
|
private buildCommonRequestOptions (options: InternalCommonCommandOptions) {
|
||||||
const { token, expectedStatus, defaultExpectedStatus, path } = options
|
const { path } = options
|
||||||
|
|
||||||
const fallbackToken = options.implicitToken
|
|
||||||
? this.server.accessToken
|
|
||||||
: undefined
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
url: this.server.url,
|
url: this.server.url,
|
||||||
path,
|
path,
|
||||||
|
|
||||||
token: token !== undefined ? token : fallbackToken,
|
token: this.buildCommonRequestToken(options),
|
||||||
|
statusCodeExpected: this.buildStatusCodeExpected(options)
|
||||||
statusCodeExpected: expectedStatus ?? this.expectedStatus ?? defaultExpectedStatus
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private buildCommonRequestToken (options: Pick<InternalCommonCommandOptions, 'token' | 'implicitToken'>) {
|
||||||
|
const { token } = options
|
||||||
|
|
||||||
|
const fallbackToken = options.implicitToken
|
||||||
|
? this.server.accessToken
|
||||||
|
: undefined
|
||||||
|
|
||||||
|
return token !== undefined ? token : fallbackToken
|
||||||
|
}
|
||||||
|
|
||||||
|
private buildStatusCodeExpected (options: Pick<InternalCommonCommandOptions, 'expectedStatus' | 'defaultExpectedStatus'>) {
|
||||||
|
const { expectedStatus, defaultExpectedStatus } = options
|
||||||
|
|
||||||
|
return expectedStatus ?? this.expectedStatus ?? defaultExpectedStatus
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
|
|
@ -9,7 +9,8 @@ export * from './live'
|
||||||
export * from './playlists-command'
|
export * from './playlists-command'
|
||||||
export * from './playlists'
|
export * from './playlists'
|
||||||
export * from './services-command'
|
export * from './services-command'
|
||||||
|
export * from './streaming-playlists-command'
|
||||||
|
export * from './streaming-playlists'
|
||||||
export * from './video-channels'
|
export * from './video-channels'
|
||||||
export * from './video-comments'
|
export * from './video-comments'
|
||||||
export * from './video-streaming-playlists'
|
|
||||||
export * from './videos'
|
export * from './videos'
|
||||||
|
|
45
shared/extra-utils/videos/streaming-playlists-command.ts
Normal file
45
shared/extra-utils/videos/streaming-playlists-command.ts
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
|
||||||
|
import { HttpStatusCode } from '../../core-utils/miscs/http-error-codes'
|
||||||
|
import { unwrapBody, unwrapText } from '../requests'
|
||||||
|
import { AbstractCommand, OverrideCommandOptions } from '../shared'
|
||||||
|
|
||||||
|
export class StreamingPlaylistsCommand extends AbstractCommand {
|
||||||
|
|
||||||
|
get (options: OverrideCommandOptions & {
|
||||||
|
url: string
|
||||||
|
}) {
|
||||||
|
return unwrapText(this.getRawRequest({
|
||||||
|
...options,
|
||||||
|
|
||||||
|
url: options.url,
|
||||||
|
implicitToken: false,
|
||||||
|
defaultExpectedStatus: HttpStatusCode.OK_200
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
getSegment (options: OverrideCommandOptions & {
|
||||||
|
url: string
|
||||||
|
range?: string
|
||||||
|
}) {
|
||||||
|
return unwrapText(this.getRawRequest({
|
||||||
|
...options,
|
||||||
|
|
||||||
|
url: options.url,
|
||||||
|
range: options.range,
|
||||||
|
implicitToken: false,
|
||||||
|
defaultExpectedStatus: HttpStatusCode.OK_200,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
getSegmentSha256 (options: OverrideCommandOptions & {
|
||||||
|
url: string
|
||||||
|
}) {
|
||||||
|
return unwrapBody<{ [ id: string ]: string }>(this.getRawRequest({
|
||||||
|
...options,
|
||||||
|
|
||||||
|
url: options.url,
|
||||||
|
implicitToken: false,
|
||||||
|
defaultExpectedStatus: HttpStatusCode.OK_200
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
76
shared/extra-utils/videos/streaming-playlists.ts
Normal file
76
shared/extra-utils/videos/streaming-playlists.ts
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
import { expect } from 'chai'
|
||||||
|
import { sha256 } from '@server/helpers/core-utils'
|
||||||
|
import { HttpStatusCode } from '@shared/core-utils'
|
||||||
|
import { VideoStreamingPlaylist } from '@shared/models'
|
||||||
|
import { ServerInfo } from '../server'
|
||||||
|
|
||||||
|
async function checkSegmentHash (options: {
|
||||||
|
server: ServerInfo
|
||||||
|
baseUrlPlaylist: string
|
||||||
|
baseUrlSegment: string
|
||||||
|
videoUUID: string
|
||||||
|
resolution: number
|
||||||
|
hlsPlaylist: VideoStreamingPlaylist
|
||||||
|
}) {
|
||||||
|
const { server, baseUrlPlaylist, baseUrlSegment, videoUUID, resolution, hlsPlaylist } = options
|
||||||
|
const command = server.streamingPlaylistsCommand
|
||||||
|
|
||||||
|
const playlist = await command.get({ url: `${baseUrlPlaylist}/${videoUUID}/${resolution}.m3u8` })
|
||||||
|
|
||||||
|
const videoName = `${videoUUID}-${resolution}-fragmented.mp4`
|
||||||
|
|
||||||
|
const matches = /#EXT-X-BYTERANGE:(\d+)@(\d+)/.exec(playlist)
|
||||||
|
|
||||||
|
const length = parseInt(matches[1], 10)
|
||||||
|
const offset = parseInt(matches[2], 10)
|
||||||
|
const range = `${offset}-${offset + length - 1}`
|
||||||
|
|
||||||
|
const segmentBody = await command.getSegment({
|
||||||
|
url: `${baseUrlSegment}/${videoUUID}/${videoName}`,
|
||||||
|
expectedStatus: HttpStatusCode.PARTIAL_CONTENT_206,
|
||||||
|
range: `bytes=${range}`
|
||||||
|
})
|
||||||
|
|
||||||
|
const shaBody = await command.getSegmentSha256({ url: hlsPlaylist.segmentsSha256Url })
|
||||||
|
expect(sha256(segmentBody)).to.equal(shaBody[videoName][range])
|
||||||
|
}
|
||||||
|
|
||||||
|
async function checkLiveSegmentHash (options: {
|
||||||
|
server: ServerInfo
|
||||||
|
baseUrlSegment: string
|
||||||
|
videoUUID: string
|
||||||
|
segmentName: string
|
||||||
|
hlsPlaylist: VideoStreamingPlaylist
|
||||||
|
}) {
|
||||||
|
const { server, baseUrlSegment, videoUUID, segmentName, hlsPlaylist } = options
|
||||||
|
const command = server.streamingPlaylistsCommand
|
||||||
|
|
||||||
|
const segmentBody = await command.getSegment({ url: `${baseUrlSegment}/${videoUUID}/${segmentName}` })
|
||||||
|
const shaBody = await command.getSegmentSha256({ url: hlsPlaylist.segmentsSha256Url })
|
||||||
|
|
||||||
|
expect(sha256(segmentBody)).to.equal(shaBody[segmentName])
|
||||||
|
}
|
||||||
|
|
||||||
|
async function checkResolutionsInMasterPlaylist (options: {
|
||||||
|
server: ServerInfo
|
||||||
|
playlistUrl: string
|
||||||
|
resolutions: number[]
|
||||||
|
}) {
|
||||||
|
const { server, playlistUrl, resolutions } = options
|
||||||
|
|
||||||
|
const masterPlaylist = await server.streamingPlaylistsCommand.get({ url: playlistUrl })
|
||||||
|
|
||||||
|
for (const resolution of resolutions) {
|
||||||
|
const reg = new RegExp(
|
||||||
|
'#EXT-X-STREAM-INF:BANDWIDTH=\\d+,RESOLUTION=\\d+x' + resolution + ',(FRAME-RATE=\\d+,)?CODECS="avc1.64001f,mp4a.40.2"'
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(masterPlaylist).to.match(reg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
checkSegmentHash,
|
||||||
|
checkLiveSegmentHash,
|
||||||
|
checkResolutionsInMasterPlaylist
|
||||||
|
}
|
|
@ -1,82 +0,0 @@
|
||||||
import { makeRawRequest } from '../requests/requests'
|
|
||||||
import { sha256 } from '../../../server/helpers/core-utils'
|
|
||||||
import { VideoStreamingPlaylist } from '../../models/videos/video-streaming-playlist.model'
|
|
||||||
import { expect } from 'chai'
|
|
||||||
import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes'
|
|
||||||
|
|
||||||
function getPlaylist (url: string, statusCodeExpected = HttpStatusCode.OK_200) {
|
|
||||||
return makeRawRequest(url, statusCodeExpected)
|
|
||||||
}
|
|
||||||
|
|
||||||
function getSegment (url: string, statusCodeExpected = HttpStatusCode.OK_200, range?: string) {
|
|
||||||
return makeRawRequest(url, statusCodeExpected, range)
|
|
||||||
}
|
|
||||||
|
|
||||||
function getSegmentSha256 (url: string, statusCodeExpected = HttpStatusCode.OK_200) {
|
|
||||||
return makeRawRequest(url, statusCodeExpected)
|
|
||||||
}
|
|
||||||
|
|
||||||
async function checkSegmentHash (
|
|
||||||
baseUrlPlaylist: string,
|
|
||||||
baseUrlSegment: string,
|
|
||||||
videoUUID: string,
|
|
||||||
resolution: number,
|
|
||||||
hlsPlaylist: VideoStreamingPlaylist
|
|
||||||
) {
|
|
||||||
const res = await getPlaylist(`${baseUrlPlaylist}/${videoUUID}/${resolution}.m3u8`)
|
|
||||||
const playlist = res.text
|
|
||||||
|
|
||||||
const videoName = `${videoUUID}-${resolution}-fragmented.mp4`
|
|
||||||
|
|
||||||
const matches = /#EXT-X-BYTERANGE:(\d+)@(\d+)/.exec(playlist)
|
|
||||||
|
|
||||||
const length = parseInt(matches[1], 10)
|
|
||||||
const offset = parseInt(matches[2], 10)
|
|
||||||
const range = `${offset}-${offset + length - 1}`
|
|
||||||
|
|
||||||
const res2 = await getSegment(`${baseUrlSegment}/${videoUUID}/${videoName}`, HttpStatusCode.PARTIAL_CONTENT_206, `bytes=${range}`)
|
|
||||||
|
|
||||||
const resSha = await getSegmentSha256(hlsPlaylist.segmentsSha256Url)
|
|
||||||
|
|
||||||
const sha256Server = resSha.body[videoName][range]
|
|
||||||
expect(sha256(res2.body)).to.equal(sha256Server)
|
|
||||||
}
|
|
||||||
|
|
||||||
async function checkLiveSegmentHash (
|
|
||||||
baseUrlSegment: string,
|
|
||||||
videoUUID: string,
|
|
||||||
segmentName: string,
|
|
||||||
hlsPlaylist: VideoStreamingPlaylist
|
|
||||||
) {
|
|
||||||
const res2 = await getSegment(`${baseUrlSegment}/${videoUUID}/${segmentName}`)
|
|
||||||
|
|
||||||
const resSha = await getSegmentSha256(hlsPlaylist.segmentsSha256Url)
|
|
||||||
|
|
||||||
const sha256Server = resSha.body[segmentName]
|
|
||||||
expect(sha256(res2.body)).to.equal(sha256Server)
|
|
||||||
}
|
|
||||||
|
|
||||||
async function checkResolutionsInMasterPlaylist (playlistUrl: string, resolutions: number[]) {
|
|
||||||
const res = await getPlaylist(playlistUrl)
|
|
||||||
|
|
||||||
const masterPlaylist = res.text
|
|
||||||
|
|
||||||
for (const resolution of resolutions) {
|
|
||||||
const reg = new RegExp(
|
|
||||||
'#EXT-X-STREAM-INF:BANDWIDTH=\\d+,RESOLUTION=\\d+x' + resolution + ',(FRAME-RATE=\\d+,)?CODECS="avc1.64001f,mp4a.40.2"'
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(masterPlaylist).to.match(reg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
export {
|
|
||||||
getPlaylist,
|
|
||||||
getSegment,
|
|
||||||
checkResolutionsInMasterPlaylist,
|
|
||||||
getSegmentSha256,
|
|
||||||
checkLiveSegmentHash,
|
|
||||||
checkSegmentHash
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user