From b335ccec49b450052e3520f66f9acb6670e669f8 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Mon, 17 Sep 2018 15:00:46 +0200 Subject: [PATCH] Fix ios player playback/subtitles menu --- .../assets/player/peertube-videojs-plugin.ts | 243 +++++++++--------- .../src/assets/player/settings-menu-item.ts | 7 +- client/src/sass/application.scss | 2 +- .../sass/player/{player.scss => index.scss} | 0 client/src/sass/player/peertube-skin.scss | 5 +- client/src/standalone/videos/embed.scss | 2 +- package.json | 2 +- 7 files changed, 132 insertions(+), 129 deletions(-) rename client/src/sass/player/{player.scss => index.scss} (100%) diff --git a/client/src/assets/player/peertube-videojs-plugin.ts b/client/src/assets/player/peertube-videojs-plugin.ts index 4b0677fab..36b80bd72 100644 --- a/client/src/assets/player/peertube-videojs-plugin.ts +++ b/client/src/assets/player/peertube-videojs-plugin.ts @@ -4,7 +4,7 @@ import { VideoFile } from '../../../../shared/models/videos/video.model' import { renderVideo } from './video-renderer' import './settings-menu-button' import { PeertubePluginOptions, VideoJSCaption, VideoJSComponentInterface, videojsUntyped } from './peertube-videojs-typings' -import { isMobile, videoFileMaxByResolution, videoFileMinByResolution, timeToInt } from './utils' +import { isMobile, timeToInt, videoFileMaxByResolution, videoFileMinByResolution } from './utils' import * as CacheChunkStore from 'cache-chunk-store' import { PeertubeChunkStore } from './peertube-chunk-store' import { @@ -83,11 +83,6 @@ class PeerTubePlugin extends Plugin { this.videoCaptions = options.videoCaptions this.savePlayerSrcFunction = this.player.src - // Hack to "simulate" src link in video.js >= 6 - // Without this, we can't play the video after pausing it - // https://github.com/videojs/video.js/blob/master/src/js/player.js#L1633 - this.player.src = () => true - this.playerElement = options.playerElement if (this.autoplay === true) this.player.addClass('vjs-has-autoplay') @@ -104,9 +99,7 @@ class PeerTubePlugin extends Plugin { this.player.one('play', () => { // Don't run immediately scheduler, wait some seconds the TCP connections are made - this.runAutoQualitySchedulerTimer = setTimeout(() => { - this.runAutoQualityScheduler() - }, this.CONSTANTS.AUTO_QUALITY_SCHEDULER) + this.runAutoQualitySchedulerTimer = setTimeout(() => this.runAutoQualityScheduler(), this.CONSTANTS.AUTO_QUALITY_SCHEDULER) }) }) @@ -167,6 +160,9 @@ class PeerTubePlugin extends Plugin { // Do not display error to user because we will have multiple fallback this.disableErrorDisplay() + // Hack to "simulate" src link in video.js >= 6 + // Without this, we can't play the video after pausing it + // https://github.com/videojs/video.js/blob/master/src/js/player.js#L1633 this.player.src = () => true const oldPlaybackRate = this.player.playbackRate() @@ -181,102 +177,6 @@ class PeerTubePlugin extends Plugin { this.trigger('videoFileUpdate') } - addTorrent ( - magnetOrTorrentUrl: string, - previousVideoFile: VideoFile, - options: { - forcePlay?: boolean, - seek?: number, - delay?: number - }, - done: Function - ) { - console.log('Adding ' + magnetOrTorrentUrl + '.') - - const oldTorrent = this.torrent - const torrentOptions = { - store: (chunkLength, storeOpts) => new CacheChunkStore(new PeertubeChunkStore(chunkLength, storeOpts), { - max: 100 - }) - } - - this.torrent = this.webtorrent.add(magnetOrTorrentUrl, torrentOptions, torrent => { - console.log('Added ' + magnetOrTorrentUrl + '.') - - if (oldTorrent) { - // Pause the old torrent - oldTorrent.pause() - // Pause does not remove actual peers (in particular the webseed peer) - oldTorrent.removePeer(oldTorrent['ws']) - - // We use a fake renderer so we download correct pieces of the next file - if (options.delay) { - const fakeVideoElem = document.createElement('video') - renderVideo(torrent.files[0], fakeVideoElem, { autoplay: false, controls: false }, (err, renderer) => { - this.fakeRenderer = renderer - - if (err) console.error('Cannot render new torrent in fake video element.', err) - - // Load the future file at the correct time - fakeVideoElem.currentTime = this.player.currentTime() + (options.delay / 2000) - }) - } - } - - // Render the video in a few seconds? (on resolution change for example, we wait some seconds of the new video resolution) - this.addTorrentDelay = setTimeout(() => { - this.destroyFakeRenderer() - - const paused = this.player.paused() - - this.flushVideoFile(previousVideoFile) - - const renderVideoOptions = { autoplay: false, controls: true } - renderVideo(torrent.files[0], this.playerElement, renderVideoOptions,(err, renderer) => { - this.renderer = renderer - - if (err) return this.fallbackToHttp(done) - - return this.tryToPlay(err => { - if (err) return done(err) - - if (options.seek) this.seek(options.seek) - if (options.forcePlay === false && paused === true) this.player.pause() - - return done(err) - }) - }) - }, options.delay || 0) - }) - - this.torrent.on('error', err => console.error(err)) - - this.torrent.on('warning', (err: any) => { - // We don't support HTTP tracker but we don't care -> we use the web socket tracker - if (err.message.indexOf('Unsupported tracker protocol') !== -1) return - - // Users don't care about issues with WebRTC, but developers do so log it in the console - if (err.message.indexOf('Ice connection failed') !== -1) { - console.log(err) - return - } - - // Magnet hash is not up to date with the torrent file, add directly the torrent file - if (err.message.indexOf('incorrect info hash') !== -1) { - console.error('Incorrect info hash detected, falling back to torrent file.') - const newOptions = { forcePlay: true, seek: options.seek } - return this.addTorrent(this.torrent['xs'], previousVideoFile, newOptions, done) - } - - // Remote instance is down - if (err.message.indexOf('from xs param') !== -1) { - this.handleError(err) - } - - console.warn(err) - }) - } - updateResolution (resolutionId: number, delay = 0) { // Remember player state const currentTime = this.player.currentTime() @@ -336,6 +236,91 @@ class PeerTubePlugin extends Plugin { return this.torrent } + private addTorrent ( + magnetOrTorrentUrl: string, + previousVideoFile: VideoFile, + options: { + forcePlay?: boolean, + seek?: number, + delay?: number + }, + done: Function + ) { + console.log('Adding ' + magnetOrTorrentUrl + '.') + + const oldTorrent = this.torrent + const torrentOptions = { + store: (chunkLength, storeOpts) => new CacheChunkStore(new PeertubeChunkStore(chunkLength, storeOpts), { + max: 100 + }) + } + + this.torrent = this.webtorrent.add(magnetOrTorrentUrl, torrentOptions, torrent => { + console.log('Added ' + magnetOrTorrentUrl + '.') + + if (oldTorrent) { + // Pause the old torrent + this.stopTorrent(oldTorrent) + + // We use a fake renderer so we download correct pieces of the next file + if (options.delay) this.renderFileInFakeElement(torrent.files[ 0 ], options.delay) + } + + // Render the video in a few seconds? (on resolution change for example, we wait some seconds of the new video resolution) + this.addTorrentDelay = setTimeout(() => { + // We don't need the fake renderer anymore + this.destroyFakeRenderer() + + const paused = this.player.paused() + + this.flushVideoFile(previousVideoFile) + + const renderVideoOptions = { autoplay: false, controls: true } + renderVideo(torrent.files[ 0 ], this.playerElement, renderVideoOptions, (err, renderer) => { + this.renderer = renderer + + if (err) return this.fallbackToHttp(done) + + return this.tryToPlay(err => { + if (err) return done(err) + + if (options.seek) this.seek(options.seek) + if (options.forcePlay === false && paused === true) this.player.pause() + + return done(err) + }) + }) + }, options.delay || 0) + }) + + this.torrent.on('error', err => console.error(err)) + + this.torrent.on('warning', (err: any) => { + // We don't support HTTP tracker but we don't care -> we use the web socket tracker + if (err.message.indexOf('Unsupported tracker protocol') !== -1) return + + // Users don't care about issues with WebRTC, but developers do so log it in the console + if (err.message.indexOf('Ice connection failed') !== -1) { + console.log(err) + return + } + + // Magnet hash is not up to date with the torrent file, add directly the torrent file + if (err.message.indexOf('incorrect info hash') !== -1) { + console.error('Incorrect info hash detected, falling back to torrent file.') + const newOptions = { forcePlay: true, seek: options.seek } + return this.addTorrent(this.torrent[ 'xs' ], previousVideoFile, newOptions, done) + } + + // Remote instance is down + if (err.message.indexOf('from xs param') !== -1) { + this.handleError(err) + } + + console.warn(err) + }) + } + private tryToPlay (done?: Function) { if (!done) done = function () { /* empty */ } @@ -435,22 +420,22 @@ class PeerTubePlugin extends Plugin { if (this.autoplay === true) { this.player.posterImage.hide() + return this.updateVideoFile(undefined, { forcePlay: true, seek: this.startTime }) + } + + // Don't try on iOS that does not support MediaSource + if (this.isIOS()) { + this.currentVideoFile = this.pickAverageVideoFile() + return this.fallbackToHttp(undefined, false) + } + + // Proxy first play + const oldPlay = this.player.play.bind(this.player) + this.player.play = () => { + this.player.addClass('vjs-has-big-play-button-clicked') + this.player.play = oldPlay + this.updateVideoFile(undefined, { forcePlay: true, seek: this.startTime }) - } else { - // Don't try on iOS that does not support MediaSource - if (this.isIOS()) { - this.currentVideoFile = this.pickAverageVideoFile() - return this.fallbackToHttp(undefined, false) - } - - // Proxy first play - const oldPlay = this.player.play.bind(this.player) - this.player.play = () => { - this.player.addClass('vjs-has-big-play-button-clicked') - this.player.play = oldPlay - - this.updateVideoFile(undefined, { forcePlay: true, seek: this.startTime }) - } } } @@ -607,6 +592,24 @@ class PeerTubePlugin extends Plugin { return this.videoFiles[Math.floor(this.videoFiles.length / 2)] } + private stopTorrent (torrent: WebTorrent.Torrent) { + torrent.pause() + // Pause does not remove actual peers (in particular the webseed peer) + torrent.removePeer(torrent[ 'ws' ]) + } + + private renderFileInFakeElement (file: WebTorrent.TorrentFile, delay: number) { + const fakeVideoElem = document.createElement('video') + renderVideo(file, fakeVideoElem, { autoplay: false, controls: false }, (err, renderer) => { + this.fakeRenderer = renderer + + if (err) console.error('Cannot render new torrent in fake video element.', err) + + // Load the future file at the correct time (in delay MS - 2 seconds) + fakeVideoElem.currentTime = this.player.currentTime() + (delay - 2000) + }) + } + private destroyFakeRenderer () { if (this.fakeRenderer) { if (this.fakeRenderer.destroy) { diff --git a/client/src/assets/player/settings-menu-item.ts b/client/src/assets/player/settings-menu-item.ts index 6e2224e20..f6cf6d0f3 100644 --- a/client/src/assets/player/settings-menu-item.ts +++ b/client/src/assets/player/settings-menu-item.ts @@ -38,8 +38,11 @@ class SettingsMenuItem extends MenuItem { this.eventHandlers() player.ready(() => { - this.build() - this.reset() + // Voodoo magic for IOS + setTimeout(() => { + this.build() + this.reset() + }, 0) }) } diff --git a/client/src/sass/application.scss b/client/src/sass/application.scss index 8d2bfb077..c1135cd02 100644 --- a/client/src/sass/application.scss +++ b/client/src/sass/application.scss @@ -9,7 +9,7 @@ $icon-font-path: '../../node_modules/@neos21/bootstrap3-glyphicons/assets/fonts/ @import '~video.js/dist/video-js.css'; $assets-path: '../assets/'; -@import './player/player'; +@import './player/index'; @import './loading-bar'; @import './primeng-custom'; diff --git a/client/src/sass/player/player.scss b/client/src/sass/player/index.scss similarity index 100% rename from client/src/sass/player/player.scss rename to client/src/sass/player/index.scss diff --git a/client/src/sass/player/peertube-skin.scss b/client/src/sass/player/peertube-skin.scss index 185b00222..4e921e970 100644 --- a/client/src/sass/player/peertube-skin.scss +++ b/client/src/sass/player/peertube-skin.scss @@ -406,6 +406,7 @@ width: 37px; margin-right: 1px; + cursor: pointer; .vjs-icon-placeholder { transition: transform 0.2s ease; @@ -504,10 +505,6 @@ } } - .vjs-playback-rate { - display: none; - } - .vjs-peertube { padding: 0 !important; diff --git a/client/src/standalone/videos/embed.scss b/client/src/standalone/videos/embed.scss index 30650538f..c40ea1208 100644 --- a/client/src/standalone/videos/embed.scss +++ b/client/src/standalone/videos/embed.scss @@ -4,7 +4,7 @@ @import '~videojs-dock/dist/videojs-dock.css'; $assets-path: '../../assets/'; -@import '../../sass/player/player'; +@import '../../sass/player/index'; [hidden] { display: none !important; diff --git a/package.json b/package.json index 1cb5be181..53e07a72b 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,7 @@ }, "lint-staged": { "*.scss": [ - "sass-lint -c .sass-lint.yml", + "sass-lint -c client/.sass-lint.yml", "git add" ] },