diff --git a/client/src/app/+my-account/my-account-settings/my-account-video-settings/my-account-video-settings.component.html b/client/src/app/+my-account/my-account-settings/my-account-video-settings/my-account-video-settings.component.html index 96629940f..8be8a66cc 100644 --- a/client/src/app/+my-account/my-account-settings/my-account-video-settings/my-account-video-settings.component.html +++ b/client/src/app/+my-account/my-account-settings/my-account-video-settings/my-account-video-settings.component.html @@ -15,6 +15,11 @@ + + { this.form.patchValue({ nsfwPolicy: this.user.nsfwPolicy, + webTorrentEnabled: this.user.webTorrentEnabled, autoPlayVideo: this.user.autoPlayVideo === true }) }) @@ -42,9 +44,11 @@ export class MyAccountVideoSettingsComponent extends FormReactive implements OnI updateDetails () { const nsfwPolicy = this.form.value['nsfwPolicy'] + const webTorrentEnabled = this.form.value['webTorrentEnabled'] const autoPlayVideo = this.form.value['autoPlayVideo'] const details: UserUpdateMe = { nsfwPolicy, + webTorrentEnabled, autoPlayVideo } diff --git a/client/src/app/core/auth/auth-user.model.ts b/client/src/app/core/auth/auth-user.model.ts index 74ed1c580..acd13d9c5 100644 --- a/client/src/app/core/auth/auth-user.model.ts +++ b/client/src/app/core/auth/auth-user.model.ts @@ -72,6 +72,7 @@ export class AuthUser extends User { EMAIL: 'email', USERNAME: 'username', NSFW_POLICY: 'nsfw_policy', + WEBTORRENT_ENABLED: 'peertube-videojs-' + 'webtorrent_enabled', AUTO_PLAY_VIDEO: 'auto_play_video' } @@ -87,6 +88,7 @@ export class AuthUser extends User { email: peertubeLocalStorage.getItem(this.KEYS.EMAIL), role: parseInt(peertubeLocalStorage.getItem(this.KEYS.ROLE), 10) as UserRole, nsfwPolicy: peertubeLocalStorage.getItem(this.KEYS.NSFW_POLICY) as NSFWPolicyType, + webTorrentEnabled: peertubeLocalStorage.getItem(this.KEYS.WEBTORRENT_ENABLED) === 'true', autoPlayVideo: peertubeLocalStorage.getItem(this.KEYS.AUTO_PLAY_VIDEO) === 'true' }, Tokens.load() @@ -101,6 +103,7 @@ export class AuthUser extends User { peertubeLocalStorage.removeItem(this.KEYS.ID) peertubeLocalStorage.removeItem(this.KEYS.ROLE) peertubeLocalStorage.removeItem(this.KEYS.NSFW_POLICY) + peertubeLocalStorage.removeItem(this.KEYS.WEBTORRENT_ENABLED) peertubeLocalStorage.removeItem(this.KEYS.AUTO_PLAY_VIDEO) peertubeLocalStorage.removeItem(this.KEYS.EMAIL) Tokens.flush() @@ -138,6 +141,7 @@ export class AuthUser extends User { peertubeLocalStorage.setItem(AuthUser.KEYS.EMAIL, this.email) peertubeLocalStorage.setItem(AuthUser.KEYS.ROLE, this.role.toString()) peertubeLocalStorage.setItem(AuthUser.KEYS.NSFW_POLICY, this.nsfwPolicy.toString()) + peertubeLocalStorage.setItem(AuthUser.KEYS.WEBTORRENT_ENABLED, JSON.stringify(this.webTorrentEnabled)) peertubeLocalStorage.setItem(AuthUser.KEYS.AUTO_PLAY_VIDEO, JSON.stringify(this.autoPlayVideo)) this.tokens.save() } diff --git a/client/src/app/shared/users/user.model.ts b/client/src/app/shared/users/user.model.ts index 877f1bf3a..7c840ffa7 100644 --- a/client/src/app/shared/users/user.model.ts +++ b/client/src/app/shared/users/user.model.ts @@ -18,6 +18,7 @@ export type UserConstructorHash = { videoQuota?: number, videoQuotaDaily?: number, nsfwPolicy?: NSFWPolicyType, + webTorrentEnabled?: boolean, autoPlayVideo?: boolean, createdAt?: Date, account?: AccountServerModel, @@ -32,6 +33,7 @@ export class User implements UserServerModel { email: string role: UserRole nsfwPolicy: NSFWPolicyType + webTorrentEnabled: boolean autoPlayVideo: boolean videoQuota: number videoQuotaDaily: number @@ -52,6 +54,7 @@ export class User implements UserServerModel { this.videoQuota = hash.videoQuota this.videoQuotaDaily = hash.videoQuotaDaily this.nsfwPolicy = hash.nsfwPolicy + this.webTorrentEnabled = hash.webTorrentEnabled this.autoPlayVideo = hash.autoPlayVideo this.createdAt = hash.createdAt this.blocked = hash.blocked diff --git a/client/src/assets/player/peertube-player-local-storage.ts b/client/src/assets/player/peertube-player-local-storage.ts index dac54c5a4..3ac5fe58a 100644 --- a/client/src/assets/player/peertube-player-local-storage.ts +++ b/client/src/assets/player/peertube-player-local-storage.ts @@ -10,6 +10,13 @@ function getStoredVolume () { return undefined } +function getStoredWebTorrentEnabled (): boolean { + const value = getLocalStorage('webtorrent_enabled') + if (value !== null && value !== undefined) return value === 'true' + + return false +} + function getStoredMute () { const value = getLocalStorage('mute') if (value !== null && value !== undefined) return value === 'true' @@ -56,6 +63,7 @@ function getAverageBandwidthInStore () { export { getStoredVolume, + getStoredWebTorrentEnabled, getStoredMute, getStoredTheater, saveVolumeInStore, diff --git a/client/src/assets/player/peertube-videojs-plugin.ts b/client/src/assets/player/peertube-videojs-plugin.ts index 2330f476f..5cebab6d9 100644 --- a/client/src/assets/player/peertube-videojs-plugin.ts +++ b/client/src/assets/player/peertube-videojs-plugin.ts @@ -11,11 +11,18 @@ import { getAverageBandwidthInStore, getStoredMute, getStoredVolume, + getStoredWebTorrentEnabled, saveAverageBandwidth, saveMuteInStore, saveVolumeInStore } from './peertube-player-local-storage' +type PlayOptions = { + forcePlay?: boolean, + seek?: number, + delay?: number +} + const Plugin: VideoJSComponentInterface = videojs.getPlugin('plugin') class PeerTubePlugin extends Plugin { private readonly playerElement: HTMLVideoElement @@ -64,6 +71,7 @@ class PeerTubePlugin extends Plugin { private autoResolution = true private forbidAutoResolution = false private isAutoResolutionObservation = false + private playerRefusedP2P = false private videoViewInterval private torrentInfoInterval @@ -80,6 +88,7 @@ class PeerTubePlugin extends Plugin { // Disable auto play on iOS this.autoplay = options.autoplay && this.isIOS() === false + this.playerRefusedP2P = !getStoredWebTorrentEnabled() this.startTime = timeToInt(options.startTime) this.videoFiles = options.videoFiles @@ -178,6 +187,15 @@ class PeerTubePlugin extends Plugin { const previousVideoFile = this.currentVideoFile this.currentVideoFile = videoFile + // Don't try on iOS that does not support MediaSource + // Or don't use P2P if webtorrent is disabled + if (this.isIOS() || this.playerRefusedP2P) { + return this.fallbackToHttp(options, () => { + this.player.playbackRate(oldPlaybackRate) + return done() + }) + } + this.addTorrent(this.currentVideoFile.magnetUri, previousVideoFile, options, () => { this.player.playbackRate(oldPlaybackRate) return done() @@ -248,11 +266,7 @@ class PeerTubePlugin extends Plugin { private addTorrent ( magnetOrTorrentUrl: string, previousVideoFile: VideoFile, - options: { - forcePlay?: boolean, - seek?: number, - delay?: number - }, + options: PlayOptions, done: Function ) { console.log('Adding ' + magnetOrTorrentUrl + '.') @@ -288,7 +302,7 @@ class PeerTubePlugin extends Plugin { renderVideo(torrent.files[ 0 ], this.playerElement, renderVideoOptions, (err, renderer) => { this.renderer = renderer - if (err) return this.fallbackToHttp(done) + if (err) return this.fallbackToHttp(options, done) return this.tryToPlay(err => { if (err) return done(err) @@ -296,7 +310,7 @@ class PeerTubePlugin extends Plugin { if (options.seek) this.seek(options.seek) if (options.forcePlay === false && paused === true) this.player.pause() - return done(err) + return done() }) }) }, options.delay || 0) @@ -432,12 +446,6 @@ class PeerTubePlugin extends Plugin { 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 = () => { @@ -567,7 +575,9 @@ class PeerTubePlugin extends Plugin { return fetch(url, { method: 'PUT', body, headers }) } - private fallbackToHttp (done?: Function, play = true) { + private fallbackToHttp (options: PlayOptions, done?: Function) { + const paused = this.player.paused() + this.disableAutoResolution(true) this.flushVideoFile(this.currentVideoFile, true) @@ -579,9 +589,15 @@ class PeerTubePlugin extends Plugin { const httpUrl = this.currentVideoFile.fileUrl this.player.src = this.savePlayerSrcFunction this.player.src(httpUrl) - if (play) this.tryToPlay() - if (done) return done() + return this.tryToPlay(err => { + if (err && done) return done(err) + + if (options.seek) this.seek(options.seek) + if (options.forcePlay === false && paused === true) this.player.pause() + + if (done) return done() + }) } private handleError (err: Error | string) { diff --git a/server/controllers/api/users/me.ts b/server/controllers/api/users/me.ts index ebe668110..82299747d 100644 --- a/server/controllers/api/users/me.ts +++ b/server/controllers/api/users/me.ts @@ -328,6 +328,7 @@ async function updateMe (req: express.Request, res: express.Response, next: expr if (body.password !== undefined) user.password = body.password if (body.email !== undefined) user.email = body.email if (body.nsfwPolicy !== undefined) user.nsfwPolicy = body.nsfwPolicy + if (body.webTorrentEnabled !== undefined) user.webTorrentEnabled = body.webTorrentEnabled if (body.autoPlayVideo !== undefined) user.autoPlayVideo = body.autoPlayVideo await sequelizeTypescript.transaction(async t => { diff --git a/server/helpers/custom-validators/users.ts b/server/helpers/custom-validators/users.ts index 90fc74a48..1cb5e5b0f 100644 --- a/server/helpers/custom-validators/users.ts +++ b/server/helpers/custom-validators/users.ts @@ -42,6 +42,10 @@ function isUserNSFWPolicyValid (value: any) { return exists(value) && nsfwPolicies.indexOf(value) !== -1 } +function isUserWebTorrentEnabledValid (value: any) { + return isBooleanValid(value) +} + function isUserAutoPlayVideoValid (value: any) { return isBooleanValid(value) } @@ -78,6 +82,7 @@ export { isUserUsernameValid, isUserEmailVerifiedValid, isUserNSFWPolicyValid, + isUserWebTorrentEnabledValid, isUserAutoPlayVideoValid, isUserDisplayNameValid, isUserDescriptionValid, diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts index cf00da2c7..03158e356 100644 --- a/server/initializers/constants.ts +++ b/server/initializers/constants.ts @@ -16,7 +16,7 @@ let config: IConfig = require('config') // --------------------------------------------------------------------------- -const LAST_MIGRATION_VERSION = 275 +const LAST_MIGRATION_VERSION = 280 // --------------------------------------------------------------------------- diff --git a/server/initializers/migrations/0280-webtorrent-policy-user.ts b/server/initializers/migrations/0280-webtorrent-policy-user.ts new file mode 100644 index 000000000..e6488356a --- /dev/null +++ b/server/initializers/migrations/0280-webtorrent-policy-user.ts @@ -0,0 +1,28 @@ +import * as Sequelize from 'sequelize' + +async function up (utils: { + transaction: Sequelize.Transaction + queryInterface: Sequelize.QueryInterface + sequelize: Sequelize.Sequelize +}): Promise { + { + const data = { + type: Sequelize.BOOLEAN, + allowNull: false, + defaultValue: true + } + + await utils.queryInterface.addColumn('user', 'webTorrentEnabled', data) + } + +} + +async function down (utils: { + transaction: Sequelize.Transaction + queryInterface: Sequelize.QueryInterface + sequelize: Sequelize.Sequelize +}): Promise { + await utils.queryInterface.removeColumn('user', 'webTorrentEnabled') +} + +export { up, down } diff --git a/server/models/account/user.ts b/server/models/account/user.ts index 39654cfcf..4b4a562fa 100644 --- a/server/models/account/user.ts +++ b/server/models/account/user.ts @@ -31,7 +31,8 @@ import { isUserRoleValid, isUserUsernameValid, isUserVideoQuotaDailyValid, - isUserVideoQuotaValid + isUserVideoQuotaValid, + isUserWebTorrentEnabledValid } from '../../helpers/custom-validators/users' import { comparePassword, cryptPassword } from '../../helpers/peertube-crypto' import { OAuthTokenModel } from '../oauth/oauth-token' @@ -107,6 +108,11 @@ export class UserModel extends Model { @Column(DataType.ENUM(values(NSFW_POLICY_TYPES))) nsfwPolicy: NSFWPolicyType + @AllowNull(false) + @Is('UserWebTorrentEnabled', value => throwIfNotValid(value, isUserWebTorrentEnabledValid, 'WebTorrent enabled')) + @Column + webTorrentEnabled: boolean + @AllowNull(false) @Default(true) @Is('UserAutoPlayVideo', value => throwIfNotValid(value, isUserAutoPlayVideoValid, 'auto play video boolean')) @@ -355,6 +361,7 @@ export class UserModel extends Model { email: this.email, emailVerified: this.emailVerified, nsfwPolicy: this.nsfwPolicy, + webTorrentEnabled: this.webTorrentEnabled, autoPlayVideo: this.autoPlayVideo, role: this.role, roleLabel: USER_ROLE_LABELS[ this.role ], diff --git a/shared/models/users/user-update-me.model.ts b/shared/models/users/user-update-me.model.ts index bbffe1487..10edeee2e 100644 --- a/shared/models/users/user-update-me.model.ts +++ b/shared/models/users/user-update-me.model.ts @@ -3,7 +3,8 @@ import { NSFWPolicyType } from '../videos/nsfw-policy.type' export interface UserUpdateMe { displayName?: string description?: string - nsfwPolicy?: NSFWPolicyType + nsfwPolicy?: NSFWPolicyType, + webTorrentEnabled?: boolean, autoPlayVideo?: boolean email?: string currentPassword?: string