From 3b6f205c34bb931de0323581edf991ca33256e6b Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Thu, 24 Jan 2019 10:16:30 +0100 Subject: [PATCH] Correctly implement p2p-media-loader --- client/package.json | 2 + .../assets/player/p2p-media-loader-plugin.ts | 82 +++++++++++++++++-- .../assets/player/peertube-player-manager.ts | 12 ++- client/src/assets/player/peertube-plugin.ts | 41 +++++++++- .../assets/player/peertube-videojs-typings.ts | 12 +++ .../videojs-components/p2p-info-button.ts | 16 ++-- .../resolution-menu-button.ts | 33 +++++++- .../resolution-menu-item.ts | 18 ++-- .../videojs-components/settings-menu-item.ts | 10 ++- client/src/assets/player/webtorrent-plugin.ts | 26 +++--- client/src/standalone/videos/embed.ts | 33 +++++--- client/yarn.lock | 17 +++- 12 files changed, 241 insertions(+), 61 deletions(-) diff --git a/client/package.json b/client/package.json index 9da7c1025..a455653fe 100644 --- a/client/package.json +++ b/client/package.json @@ -87,6 +87,7 @@ "@ngx-translate/i18n-polyfill": "^1.0.0", "@streamroot/videojs-hlsjs-plugin": "^1.0.7", "@types/core-js": "^2.5.0", + "@types/hls.js": "^0.12.0", "@types/jasmine": "^2.8.7", "@types/jasminewd2": "^2.0.3", "@types/jest": "^23.3.1", @@ -110,6 +111,7 @@ "extract-text-webpack-plugin": "4.0.0-beta.0", "file-loader": "^2.0.0", "focus-visible": "^4.1.5", + "hls.js": "^0.12.2", "html-loader": "^0.5.5", "html-webpack-plugin": "^3.2.0", "https-browserify": "^1.0.0", diff --git a/client/src/assets/player/p2p-media-loader-plugin.ts b/client/src/assets/player/p2p-media-loader-plugin.ts index 6d07a2c9c..25117e51e 100644 --- a/client/src/assets/player/p2p-media-loader-plugin.ts +++ b/client/src/assets/player/p2p-media-loader-plugin.ts @@ -1,25 +1,45 @@ // FIXME: something weird with our path definition in tsconfig and typings // @ts-ignore import * as videojs from 'video.js' -import { P2PMediaLoaderPluginOptions, VideoJSComponentInterface } from './peertube-videojs-typings' +import { P2PMediaLoaderPluginOptions, PlayerNetworkInfo, VideoJSComponentInterface } from './peertube-videojs-typings' // videojs-hlsjs-plugin needs videojs in window window['videojs'] = videojs import '@streamroot/videojs-hlsjs-plugin' -import { initVideoJsContribHlsJsPlayer } from 'p2p-media-loader-hlsjs' - -// import { Events } from '../p2p-media-loader/p2p-media-loader-core/lib' +import { Engine, initVideoJsContribHlsJsPlayer } from 'p2p-media-loader-hlsjs' +import * as Hls from 'hls.js' +import { Events } from 'p2p-media-loader-core' const Plugin: VideoJSComponentInterface = videojs.getPlugin('plugin') class P2pMediaLoaderPlugin extends Plugin { + private readonly CONSTANTS = { + INFO_SCHEDULER: 1000 // Don't change this + } + + private hlsjs: Hls + private p2pEngine: Engine + private statsP2PBytes = { + pendingDownload: [] as number[], + pendingUpload: [] as number[], + numPeers: 0, + totalDownload: 0, + totalUpload: 0 + } + + private networkInfoInterval: any + constructor (player: videojs.Player, options: P2PMediaLoaderPluginOptions) { super(player, options) - initVideoJsContribHlsJsPlayer(player) + videojs.Html5Hlsjs.addHook('beforeinitialize', (videojsPlayer: any, hlsjs: Hls) => { + this.hlsjs = hlsjs - console.log(options) + this.initialize() + }) + + initVideoJsContribHlsJsPlayer(player) player.src({ type: options.type, @@ -27,6 +47,56 @@ class P2pMediaLoaderPlugin extends Plugin { }) } + dispose () { + clearInterval(this.networkInfoInterval) + } + + private initialize () { + this.p2pEngine = this.player.tech_.options_.hlsjsConfig.loader.getEngine() + + this.hlsjs.on(Hls.Events.LEVEL_SWITCHING, (_, data: Hls.levelSwitchingData) => { + this.trigger('resolutionChange', { auto: this.hlsjs.autoLevelEnabled, resolutionId: data.height }) + }) + + this.runStats() + } + + private runStats () { + this.p2pEngine.on(Events.PieceBytesDownloaded, (method: string, size: number) => { + if (method === 'p2p') { + this.statsP2PBytes.pendingDownload.push(size) + this.statsP2PBytes.totalDownload += size + } + }) + + this.p2pEngine.on(Events.PieceBytesUploaded, (method: string, size: number) => { + if (method === 'p2p') { + this.statsP2PBytes.pendingUpload.push(size) + this.statsP2PBytes.totalUpload += size + } + }) + + this.p2pEngine.on(Events.PeerConnect, () => this.statsP2PBytes.numPeers++) + this.p2pEngine.on(Events.PeerClose, () => this.statsP2PBytes.numPeers--) + + this.networkInfoInterval = setInterval(() => { + let downloadSpeed = this.statsP2PBytes.pendingDownload.reduce((a: number, b: number) => a + b, 0) + let uploadSpeed = this.statsP2PBytes.pendingUpload.reduce((a: number, b: number) => a + b, 0) + + this.statsP2PBytes.pendingDownload = [] + this.statsP2PBytes.pendingUpload = [] + + return this.player.trigger('p2pInfo', { + p2p: { + downloadSpeed, + uploadSpeed, + numPeers: this.statsP2PBytes.numPeers, + downloaded: this.statsP2PBytes.totalDownload, + uploaded: this.statsP2PBytes.totalUpload + } + } as PlayerNetworkInfo) + }, this.CONSTANTS.INFO_SCHEDULER) + } } videojs.registerPlugin('p2pMediaLoader', P2pMediaLoaderPlugin) diff --git a/client/src/assets/player/peertube-player-manager.ts b/client/src/assets/player/peertube-player-manager.ts index 9155c0698..2e090847c 100644 --- a/client/src/assets/player/peertube-player-manager.ts +++ b/client/src/assets/player/peertube-player-manager.ts @@ -24,17 +24,17 @@ videojsUntyped.getComponent('CaptionsButton').prototype.controlText_ = 'Subtitle // We just want to display 'Off' instead of 'captions off', keep a space so the variable == true (hacky I know) videojsUntyped.getComponent('CaptionsButton').prototype.label_ = ' ' -type PlayerMode = 'webtorrent' | 'p2p-media-loader' +export type PlayerMode = 'webtorrent' | 'p2p-media-loader' -type WebtorrentOptions = { +export type WebtorrentOptions = { videoFiles: VideoFile[] } -type P2PMediaLoaderOptions = { +export type P2PMediaLoaderOptions = { playlistUrl: string } -type CommonOptions = { +export type CommonOptions = { playerElement: HTMLVideoElement autoplay: boolean @@ -137,6 +137,7 @@ export class PeertubePlayerManager { const commonOptions = options.common const webtorrentOptions = options.webtorrent const p2pMediaLoaderOptions = options.p2pMediaLoader + let html5 = {} const plugins: VideoJSPluginOptions = { peertube: { @@ -171,6 +172,7 @@ export class PeertubePlayerManager { } Object.assign(plugins, { p2pMediaLoader, streamrootHls }) + html5 = streamrootHls.html5 } if (webtorrentOptions) { @@ -184,6 +186,8 @@ export class PeertubePlayerManager { } const videojsOptions = { + html5, + // We don't use text track settings for now textTrackSettings: false, controls: commonOptions.controls !== undefined ? commonOptions.controls : true, diff --git a/client/src/assets/player/peertube-plugin.ts b/client/src/assets/player/peertube-plugin.ts index 0bd607697..f83d9094a 100644 --- a/client/src/assets/player/peertube-plugin.ts +++ b/client/src/assets/player/peertube-plugin.ts @@ -2,7 +2,14 @@ // @ts-ignore import * as videojs from 'video.js' import './videojs-components/settings-menu-button' -import { PeerTubePluginOptions, UserWatching, VideoJSCaption, VideoJSComponentInterface, videojsUntyped } from './peertube-videojs-typings' +import { + PeerTubePluginOptions, + ResolutionUpdateData, + UserWatching, + VideoJSCaption, + VideoJSComponentInterface, + videojsUntyped +} from './peertube-videojs-typings' import { isMobile, timeToInt } from './utils' import { getStoredLastSubtitle, @@ -30,6 +37,7 @@ class PeerTubePlugin extends Plugin { private videoViewInterval: any private userWatchingVideoInterval: any private qualityObservationTimer: any + private lastResolutionChange: ResolutionUpdateData constructor (player: videojs.Player, options: PeerTubePluginOptions) { super(player, options) @@ -44,6 +52,22 @@ class PeerTubePlugin extends Plugin { this.player.ready(() => { const playerOptions = this.player.options_ + if (this.player.webtorrent) { + this.player.webtorrent().on('resolutionChange', (_: any, d: any) => this.handleResolutionChange(d)) + this.player.webtorrent().on('autoResolutionChange', (_: any, d: any) => this.trigger('autoResolutionChange', d)) + } + + if (this.player.p2pMediaLoader) { + this.player.p2pMediaLoader().on('resolutionChange', (_: any, d: any) => this.handleResolutionChange(d)) + } + + this.player.tech_.on('loadedqualitydata', () => { + setTimeout(() => { + // Replay a resolution change, now we loaded all quality data + if (this.lastResolutionChange) this.handleResolutionChange(this.lastResolutionChange) + }, 0) + }) + const volume = getStoredVolume() if (volume !== undefined) this.player.volume(volume) @@ -158,6 +182,21 @@ class PeerTubePlugin extends Plugin { return fetch(url, { method: 'PUT', body, headers }) } + private handleResolutionChange (data: ResolutionUpdateData) { + this.lastResolutionChange = data + + const qualityLevels = this.player.qualityLevels() + + for (let i = 0; i < qualityLevels.length; i++) { + if (qualityLevels[i].height === data.resolutionId) { + data.id = qualityLevels[i].id + break + } + } + + this.trigger('resolutionChange', data) + } + private alterInactivity () { let saveInactivityTimeout: number diff --git a/client/src/assets/player/peertube-videojs-typings.ts b/client/src/assets/player/peertube-videojs-typings.ts index 060ea4dce..fff992a6f 100644 --- a/client/src/assets/player/peertube-videojs-typings.ts +++ b/client/src/assets/player/peertube-videojs-typings.ts @@ -83,13 +83,25 @@ type LoadedQualityData = { type ResolutionUpdateData = { auto: boolean, resolutionId: number + id?: number } type AutoResolutionUpdateData = { possible: boolean } +type PlayerNetworkInfo = { + p2p: { + downloadSpeed: number + uploadSpeed: number + downloaded: number + uploaded: number + numPeers: number + } +} + export { + PlayerNetworkInfo, ResolutionUpdateData, AutoResolutionUpdateData, VideoJSComponentInterface, diff --git a/client/src/assets/player/videojs-components/p2p-info-button.ts b/client/src/assets/player/videojs-components/p2p-info-button.ts index 03a5d29f0..2fc4c4562 100644 --- a/client/src/assets/player/videojs-components/p2p-info-button.ts +++ b/client/src/assets/player/videojs-components/p2p-info-button.ts @@ -1,4 +1,4 @@ -import { VideoJSComponentInterface, videojsUntyped } from '../peertube-videojs-typings' +import { PlayerNetworkInfo, VideoJSComponentInterface, videojsUntyped } from '../peertube-videojs-typings' import { bytes } from '../utils' const Button: VideoJSComponentInterface = videojsUntyped.getComponent('Button') @@ -65,7 +65,7 @@ class P2pInfoButton extends Button { subDivHttp.appendChild(subDivHttpText) div.appendChild(subDivHttp) - this.player_.on('p2pInfo', (event: any, data: any) => { + this.player_.on('p2pInfo', (event: any, data: PlayerNetworkInfo) => { // We are in HTTP fallback if (!data) { subDivHttp.className = 'vjs-peertube-displayed' @@ -74,11 +74,13 @@ class P2pInfoButton extends Button { return } - const downloadSpeed = bytes(data.downloadSpeed) - const uploadSpeed = bytes(data.uploadSpeed) - const totalDownloaded = bytes(data.downloaded) - const totalUploaded = bytes(data.uploaded) - const numPeers = data.numPeers + const p2pStats = data.p2p + + const downloadSpeed = bytes(p2pStats.downloadSpeed) + const uploadSpeed = bytes(p2pStats.uploadSpeed) + const totalDownloaded = bytes(p2pStats.downloaded) + const totalUploaded = bytes(p2pStats.uploaded) + const numPeers = p2pStats.numPeers subDivWebtorrent.title = this.player_.localize('Total downloaded: ') + totalDownloaded.join(' ') + '\n' + this.player_.localize('Total uploaded: ' + totalUploaded.join(' ')) diff --git a/client/src/assets/player/videojs-components/resolution-menu-button.ts b/client/src/assets/player/videojs-components/resolution-menu-button.ts index 2847de470..abcc16411 100644 --- a/client/src/assets/player/videojs-components/resolution-menu-button.ts +++ b/client/src/assets/player/videojs-components/resolution-menu-button.ts @@ -14,11 +14,9 @@ class ResolutionMenuButton extends MenuButton { super(player, options) this.player = player - player.on('loadedqualitydata', (e: any, data: any) => this.buildQualities(data)) + player.tech_.on('loadedqualitydata', (e: any, data: any) => this.buildQualities(data)) - if (player.webtorrent) { - player.webtorrent().on('videoFileUpdate', () => setTimeout(() => this.trigger('updateLabel'), 0)) - } + player.peertube().on('resolutionChange', () => setTimeout(() => this.trigger('updateLabel'), 0)) } createEl () { @@ -49,11 +47,32 @@ class ResolutionMenuButton extends MenuButton { return 'vjs-resolution-control ' + super.buildWrapperCSSClass() } + private addClickListener (component: any) { + component.on('click', () => { + let children = this.menu.children() + + for (const child of children) { + if (component !== child) { + child.selected(false) + } + } + }) + } + private buildQualities (data: LoadedQualityData) { // The automatic resolution item will need other labels const labels: { [ id: number ]: string } = {} + data.qualityData.video.sort((a, b) => { + if (a.id > b.id) return -1 + if (a.id === b.id) return 0 + return 1 + }) + for (const d of data.qualityData.video) { + // Skip auto resolution, we'll add it ourselves + if (d.id === -1) continue + this.menu.addChild(new ResolutionMenuItem( this.player_, { @@ -77,6 +96,12 @@ class ResolutionMenuButton extends MenuButton { selected: true // By default, in auto mode } )) + + for (const m of this.menu.children()) { + this.addClickListener(m) + } + + this.trigger('menuChanged') } } ResolutionMenuButton.prototype.controlText_ = 'Quality' diff --git a/client/src/assets/player/videojs-components/resolution-menu-item.ts b/client/src/assets/player/videojs-components/resolution-menu-item.ts index cc1c79739..6c42fefd2 100644 --- a/client/src/assets/player/videojs-components/resolution-menu-item.ts +++ b/client/src/assets/player/videojs-components/resolution-menu-item.ts @@ -28,16 +28,12 @@ class ResolutionMenuItem extends MenuItem { this.id = options.id this.callback = options.callback - if (player.webtorrent) { - player.webtorrent().on('videoFileUpdate', (_: any, data: ResolutionUpdateData) => this.updateSelection(data)) + player.peertube().on('resolutionChange', (_: any, data: ResolutionUpdateData) => this.updateSelection(data)) - // We only want to disable the "Auto" item - if (this.id === -1) { - player.webtorrent().on('autoResolutionUpdate', (_: any, data: AutoResolutionUpdateData) => this.updateAutoResolution(data)) - } + // We only want to disable the "Auto" item + if (this.id === -1) { + player.peertube().on('autoResolutionChange', (_: any, data: AutoResolutionUpdateData) => this.updateAutoResolution(data)) } - - // TODO: update on HLS change } handleClick (event: any) { @@ -46,12 +42,12 @@ class ResolutionMenuItem extends MenuItem { super.handleClick(event) - this.callback(this.id) + this.callback(this.id, 'video') } updateSelection (data: ResolutionUpdateData) { if (this.id === -1) { - this.currentResolutionLabel = this.labels[data.resolutionId] + this.currentResolutionLabel = this.labels[data.id] } // Automatic resolution only @@ -60,7 +56,7 @@ class ResolutionMenuItem extends MenuItem { return } - this.selected(this.id === data.resolutionId) + this.selected(this.id === data.id) } updateAutoResolution (data: AutoResolutionUpdateData) { diff --git a/client/src/assets/player/videojs-components/settings-menu-item.ts b/client/src/assets/player/videojs-components/settings-menu-item.ts index b9a430290..f14959f9c 100644 --- a/client/src/assets/player/videojs-components/settings-menu-item.ts +++ b/client/src/assets/player/videojs-components/settings-menu-item.ts @@ -223,6 +223,11 @@ class SettingsMenuItem extends MenuItem { this.subMenu.on('updateLabel', () => { this.update() }) + this.subMenu.on('menuChanged', () => { + this.bindClickEvents() + this.setSize() + this.update() + }) this.settingsSubMenuTitleEl_.innerHTML = this.player_.localize(this.subMenu.controlText_) this.settingsSubMenuEl_.appendChild(this.subMenu.menu.el_) @@ -230,7 +235,7 @@ class SettingsMenuItem extends MenuItem { this.update() this.createBackButton() - this.getSize() + this.setSize() this.bindClickEvents() // prefixed event listeners for CSS TransitionEnd @@ -292,8 +297,9 @@ class SettingsMenuItem extends MenuItem { // save size of submenus on first init // if number of submenu items change dynamically more logic will be needed - getSize () { + setSize () { this.dialog.removeClass('vjs-hidden') + videojsUntyped.dom.removeClass(this.settingsSubMenuEl_, 'vjs-hidden') this.size = this.settingsButton.getComponentSize(this.settingsSubMenuEl_) this.setMargin() this.dialog.addClass('vjs-hidden') diff --git a/client/src/assets/player/webtorrent-plugin.ts b/client/src/assets/player/webtorrent-plugin.ts index c3d990aed..47f169e24 100644 --- a/client/src/assets/player/webtorrent-plugin.ts +++ b/client/src/assets/player/webtorrent-plugin.ts @@ -5,7 +5,7 @@ import * as videojs from 'video.js' import * as WebTorrent from 'webtorrent' import { VideoFile } from '../../../../shared/models/videos/video.model' import { renderVideo } from './webtorrent/video-renderer' -import { LoadedQualityData, VideoJSComponentInterface, WebtorrentPluginOptions } from './peertube-videojs-typings' +import { LoadedQualityData, PlayerNetworkInfo, VideoJSComponentInterface, WebtorrentPluginOptions } from './peertube-videojs-typings' import { videoFileMaxByResolution, videoFileMinByResolution } from './utils' import { PeertubeChunkStore } from './webtorrent/peertube-chunk-store' import { @@ -180,7 +180,7 @@ class WebTorrentPlugin extends Plugin { }) this.changeQuality() - this.trigger('videoFileUpdate', { auto: this.autoResolution, resolutionId: this.currentVideoFile.resolution.id }) + this.trigger('resolutionChange', { auto: this.autoResolution, resolutionId: this.currentVideoFile.resolution.id }) } updateResolution (resolutionId: number, delay = 0) { @@ -216,15 +216,15 @@ class WebTorrentPlugin extends Plugin { enableAutoResolution () { this.autoResolution = true - this.trigger('videoFileUpdate', { auto: this.autoResolution, resolutionId: this.getCurrentResolutionId() }) + this.trigger('resolutionChange', { auto: this.autoResolution, resolutionId: this.getCurrentResolutionId() }) } disableAutoResolution (forbid = false) { if (forbid === true) this.autoResolutionPossible = false this.autoResolution = false - this.trigger('autoResolutionUpdate', { possible: this.autoResolutionPossible }) - this.trigger('videoFileUpdate', { auto: this.autoResolution, resolutionId: this.getCurrentResolutionId() }) + this.trigger('autoResolutionChange', { possible: this.autoResolutionPossible }) + this.trigger('resolutionChange', { auto: this.autoResolution, resolutionId: this.getCurrentResolutionId() }) } getTorrent () { @@ -472,12 +472,14 @@ class WebTorrentPlugin extends Plugin { if (this.webtorrent.downloadSpeed !== 0) this.downloadSpeeds.push(this.webtorrent.downloadSpeed) return this.player.trigger('p2pInfo', { - downloadSpeed: this.torrent.downloadSpeed, - numPeers: this.torrent.numPeers, - uploadSpeed: this.torrent.uploadSpeed, - downloaded: this.torrent.downloaded, - uploaded: this.torrent.uploaded - }) + p2p: { + downloadSpeed: this.torrent.downloadSpeed, + numPeers: this.torrent.numPeers, + uploadSpeed: this.torrent.uploadSpeed, + downloaded: this.torrent.downloaded, + uploaded: this.torrent.uploaded + } + } as PlayerNetworkInfo) }, this.CONSTANTS.INFO_SCHEDULER) } @@ -597,7 +599,7 @@ class WebTorrentPlugin extends Plugin { video: qualityLevelsPayload } } - this.player.trigger('loadedqualitydata', payload) + this.player.tech_.trigger('loadedqualitydata', payload) } private buildQualityLabel (file: VideoFile) { diff --git a/client/src/standalone/videos/embed.ts b/client/src/standalone/videos/embed.ts index b1261c4a2..0d165ea7b 100644 --- a/client/src/standalone/videos/embed.ts +++ b/client/src/standalone/videos/embed.ts @@ -23,7 +23,7 @@ import { peertubeTranslate, ResultList, VideoDetails } from '../../../../shared' import { PeerTubeResolution } from '../player/definitions' import { VideoJSCaption } from '../../assets/player/peertube-videojs-typings' import { VideoCaption } from '../../../../shared/models/videos/caption/video-caption.model' -import { PeertubePlayerManager, PeertubePlayerManagerOptions } from '../../assets/player/peertube-player-manager' +import { PeertubePlayerManager, PeertubePlayerManagerOptions, PlayerMode } from '../../assets/player/peertube-player-manager' /** * Embed API exposes control of the embed player to the outside world via @@ -162,6 +162,7 @@ class PeerTubeEmbed { subtitle: string enableApi = false startTime: number | string = 0 + mode: PlayerMode scope = 'peertube' static async main () { @@ -255,6 +256,8 @@ class PeerTubeEmbed { this.scope = this.getParamString(params, 'scope', this.scope) this.subtitle = this.getParamString(params, 'subtitle') this.startTime = this.getParamString(params, 'start') + + this.mode = this.getParamToggle(params, 'p2p-media-loader') ? 'p2p-media-loader' : 'webtorrent' } catch (err) { console.error('Cannot get params from URL.', err) } @@ -312,20 +315,26 @@ class PeerTubeEmbed { serverUrl: window.location.origin, language: navigator.language, embedUrl: window.location.origin + videoInfo.embedPath - }, - - webtorrent: { - videoFiles: videoInfo.files } - - // p2pMediaLoader: { - // // playlistUrl: 'https://akamai-axtest.akamaized.net/routes/lapd-v1-acceptance/www_c4/Manifest.m3u8' - // // playlistUrl: 'https://d2zihajmogu5jn.cloudfront.net/bipbop-advanced/bipbop_16x9_variant.m3u8' - // playlistUrl: 'https://cdn.theoplayer.com/video/elephants-dream/playlist.m3u8' - // } } - this.player = await PeertubePlayerManager.initialize('webtorrent', options) + if (this.mode === 'p2p-media-loader') { + Object.assign(options, { + p2pMediaLoader: { + // playlistUrl: 'https://akamai-axtest.akamaized.net/routes/lapd-v1-acceptance/www_c4/Manifest.m3u8' + // playlistUrl: 'https://d2zihajmogu5jn.cloudfront.net/bipbop-advanced/bipbop_16x9_variant.m3u8' + playlistUrl: 'https://cdn.theoplayer.com/video/elephants-dream/playlist.m3u8' + } + }) + } else { + Object.assign(options, { + webtorrent: { + videoFiles: videoInfo.files + } + }) + } + + this.player = await PeertubePlayerManager.initialize(this.mode, options) this.player.on('customError', (event: any, data: any) => this.handleError(data.err, serverTranslations)) diff --git a/client/yarn.lock b/client/yarn.lock index 0698ca501..ced35688f 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -411,6 +411,11 @@ resolved "https://registry.yarnpkg.com/@types/core-js/-/core-js-2.5.0.tgz#35cc282488de6f10af1d92902899a3b8ca3fbc47" integrity sha512-qjkHL3wF0JMHMqgm/kmL8Pf8rIiqvueEiZ0g6NVTcBX1WN46GWDr+V5z+gsHUeL0n8TfAmXnYmF7ajsxmBp4PQ== +"@types/hls.js@^0.12.0": + version "0.12.0" + resolved "https://registry.yarnpkg.com/@types/hls.js/-/hls.js-0.12.0.tgz#33f73e542201a766fa56792cb81fe9f97d7097ed" + integrity sha512-hJ7eJAQVEazAANK4Ay0YbXlZF36SDy9c8kcHTF7//77ylgV6hV/JrlwhVmobsSacr5aZcbw5MbZ2bSHbS36eOQ== + "@types/jasmine@*": version "3.3.1" resolved "https://registry.yarnpkg.com/@types/jasmine/-/jasmine-3.3.1.tgz#b6c4f356013364e98b583647c7b3b6de6fccd2cc" @@ -3300,7 +3305,7 @@ etag@~1.8.1: resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= -eventemitter3@^3.0.0: +eventemitter3@3.1.0, eventemitter3@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.0.tgz#090b4d6cdbd645ed10bf750d4b5407942d7ba163" integrity sha512-ivIvhpq/Y0uSjcHDcOIccjmYjGLcP09MFGE7ysAwkAvkXfpZlC985pH2/ui64DKazbTW/4kN3yqozUxlXzI6cA== @@ -4236,6 +4241,14 @@ he@1.2.x: resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== +hls.js@^0.12.2: + version "0.12.2" + resolved "https://registry.yarnpkg.com/hls.js/-/hls.js-0.12.2.tgz#64a969a78cc25991ed5de19357b1dc3f178ac23b" + integrity sha512-lQBSXggw9OzEuaUllUBoSxPcf7neFgnEiDRfCdCNdIPtUeV7vXZ0OeASx6EWtZTBiqSSPigoOX1Y+AR5dA1Feg== + dependencies: + eventemitter3 "3.1.0" + url-toolkit "^2.1.6" + hmac-drbg@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" @@ -9976,7 +9989,7 @@ url-parse@^1.4.3: querystringify "^2.0.0" requires-port "^1.0.0" -url-toolkit@^2.1.1, url-toolkit@^2.1.3: +url-toolkit@^2.1.1, url-toolkit@^2.1.3, url-toolkit@^2.1.6: version "2.1.6" resolved "https://registry.yarnpkg.com/url-toolkit/-/url-toolkit-2.1.6.tgz#6d03246499e519aad224c44044a4ae20544154f2" integrity sha512-UaZ2+50am4HwrV2crR/JAf63Q4VvPYphe63WGeoJxeu8gmOm0qxPt+KsukfakPNrX9aymGNEkkaoICwn+OuvBw==