autoplay next video support for playlists
This commit is contained in:
parent
d816f3a063
commit
bee29df8a9
|
@ -1,4 +1,6 @@
|
|||
::ng-deep svg {
|
||||
::ng-deep {
|
||||
svg {
|
||||
width: inherit;
|
||||
height: inherit;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ export class User implements UserServerModel {
|
|||
|
||||
autoPlayVideo: boolean
|
||||
autoPlayNextVideo: boolean
|
||||
autoPlayNextVideoPlaylist: boolean
|
||||
webTorrentEnabled: boolean
|
||||
videosHistoryEnabled: boolean
|
||||
videoLanguages: string[]
|
||||
|
|
|
@ -72,10 +72,6 @@ my-video-thumbnail,
|
|||
|
||||
a {
|
||||
width: auto;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline !important;
|
||||
}
|
||||
}
|
||||
|
||||
.video-info-account, .video-info-timestamp {
|
||||
|
|
|
@ -14,6 +14,17 @@
|
|||
<span>{{ currentPlaylistPosition }}</span><span>{{ playlistPagination.totalItems }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="playlist-controls">
|
||||
<my-global-icon
|
||||
iconName="videos"
|
||||
[class.active]="autoPlayNextVideoPlaylist"
|
||||
(click)="switchAutoPlayNextVideoPlaylist()"
|
||||
[ngbTooltip]="autoPlayNextVideoPlaylistSwitchText"
|
||||
placement="bottom auto"
|
||||
container="body"
|
||||
></my-global-icon>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div *ngFor="let playlistElement of playlistElements">
|
||||
|
|
|
@ -34,6 +34,21 @@
|
|||
margin: 0 3px;
|
||||
}
|
||||
}
|
||||
|
||||
.playlist-controls {
|
||||
display: flex;
|
||||
margin: 10px 0;
|
||||
|
||||
my-global-icon {
|
||||
&:not(.active) {
|
||||
opacity: .5
|
||||
}
|
||||
|
||||
::ng-deep {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
my-video-playlist-element-miniature {
|
||||
|
|
|
@ -3,9 +3,12 @@ import { VideoPlaylist } from '@app/shared/video-playlist/video-playlist.model'
|
|||
import { ComponentPagination } from '@app/shared/rest/component-pagination.model'
|
||||
import { VideoDetails, VideoPlaylistPrivacy } from '@shared/models'
|
||||
import { Router } from '@angular/router'
|
||||
import { AuthService } from '@app/core'
|
||||
import { User, UserService } from '@app/shared'
|
||||
import { AuthService, Notifier } from '@app/core'
|
||||
import { VideoPlaylistService } from '@app/shared/video-playlist/video-playlist.service'
|
||||
import { VideoPlaylistElement } from '@app/shared/video-playlist/video-playlist-element.model'
|
||||
import { peertubeLocalStorage } from '@app/shared/misc/peertube-local-storage'
|
||||
import { I18n } from '@ngx-translate/i18n-polyfill'
|
||||
|
||||
@Component({
|
||||
selector: 'my-video-watch-playlist',
|
||||
|
@ -13,6 +16,8 @@ import { VideoPlaylistElement } from '@app/shared/video-playlist/video-playlist-
|
|||
styleUrls: [ './video-watch-playlist.component.scss' ]
|
||||
})
|
||||
export class VideoWatchPlaylistComponent {
|
||||
static LOCAL_STORAGE_AUTO_PLAY_NEXT_VIDEO_PLAYLIST = 'auto_play_video_playlist'
|
||||
|
||||
@Input() video: VideoDetails
|
||||
@Input() playlist: VideoPlaylist
|
||||
|
||||
|
@ -23,14 +28,24 @@ export class VideoWatchPlaylistComponent {
|
|||
totalItems: null
|
||||
}
|
||||
|
||||
autoPlayNextVideoPlaylist: boolean
|
||||
autoPlayNextVideoPlaylistSwitchText = ''
|
||||
noPlaylistVideos = false
|
||||
currentPlaylistPosition = 1
|
||||
|
||||
constructor (
|
||||
private userService: UserService,
|
||||
private auth: AuthService,
|
||||
private notifier: Notifier,
|
||||
private i18n: I18n,
|
||||
private videoPlaylist: VideoPlaylistService,
|
||||
private router: Router
|
||||
) {}
|
||||
) {
|
||||
this.autoPlayNextVideoPlaylist = this.auth.isLoggedIn()
|
||||
? this.auth.getUser().autoPlayNextVideoPlaylist
|
||||
: peertubeLocalStorage.getItem(VideoWatchPlaylistComponent.LOCAL_STORAGE_AUTO_PLAY_NEXT_VIDEO_PLAYLIST) !== 'false'
|
||||
this.setAutoPlayNextVideoPlaylistSwitchText()
|
||||
}
|
||||
|
||||
onPlaylistVideosNearOfBottom () {
|
||||
// Last page
|
||||
|
@ -121,4 +136,33 @@ export class VideoWatchPlaylistComponent {
|
|||
this.router.navigate([],{ queryParams: { videoId: next.video.uuid, start, stop } })
|
||||
}
|
||||
}
|
||||
|
||||
switchAutoPlayNextVideoPlaylist () {
|
||||
this.autoPlayNextVideoPlaylist = !this.autoPlayNextVideoPlaylist
|
||||
this.setAutoPlayNextVideoPlaylistSwitchText()
|
||||
|
||||
peertubeLocalStorage.setItem(
|
||||
VideoWatchPlaylistComponent.LOCAL_STORAGE_AUTO_PLAY_NEXT_VIDEO_PLAYLIST,
|
||||
this.autoPlayNextVideoPlaylist.toString()
|
||||
)
|
||||
|
||||
if (this.auth.isLoggedIn()) {
|
||||
const details = {
|
||||
autoPlayNextVideoPlaylist: this.autoPlayNextVideoPlaylist
|
||||
}
|
||||
|
||||
this.userService.updateMyProfile(details).subscribe(
|
||||
() => {
|
||||
this.auth.refreshUserInformation()
|
||||
},
|
||||
err => this.notifier.error(err.message)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private setAutoPlayNextVideoPlaylistSwitchText () {
|
||||
this.autoPlayNextVideoPlaylistSwitchText = this.i18n('{{verb}} autoplay for playlists', {
|
||||
verb: this.autoPlayNextVideoPlaylist ? this.i18n('Disable') : this.i18n('Enable')
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -217,6 +217,7 @@
|
|||
<my-recommended-videos
|
||||
[inputRecommendation]="{ uuid: video.uuid, tags: video.tags }"
|
||||
[user]="user"
|
||||
[playlist]="playlist"
|
||||
(gotRecommendations)="onRecommendations($event)"
|
||||
></my-recommended-videos>
|
||||
</div>
|
||||
|
|
|
@ -37,6 +37,7 @@ import { PluginService } from '@app/core/plugins/plugin.service'
|
|||
import { HooksService } from '@app/core/plugins/hooks.service'
|
||||
import { PlatformLocation } from '@angular/common'
|
||||
import { randomInt } from '@shared/core-utils/miscs/miscs'
|
||||
import { RecommendedVideosComponent } from '../recommendations/recommended-videos.component'
|
||||
|
||||
@Component({
|
||||
selector: 'my-video-watch',
|
||||
|
@ -436,10 +437,13 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
|
|||
|
||||
this.player.one('ended', () => {
|
||||
if (this.playlist) {
|
||||
this.zone.run(() => this.videoWatchPlaylist.navigateToNextPlaylistVideo())
|
||||
if (
|
||||
this.user && this.user.autoPlayNextVideoPlaylist ||
|
||||
peertubeLocalStorage.getItem(VideoWatchPlaylistComponent.LOCAL_STORAGE_AUTO_PLAY_NEXT_VIDEO_PLAYLIST) === 'true'
|
||||
) this.zone.run(() => this.videoWatchPlaylist.navigateToNextPlaylistVideo())
|
||||
} else if (
|
||||
this.user && this.user.autoPlayNextVideo ||
|
||||
peertubeLocalStorage.getItem(VideoWatchComponent.LOCAL_STORAGE_AUTO_PLAY_NEXT_VIDEO) === 'true'
|
||||
peertubeLocalStorage.getItem(RecommendedVideosComponent.LOCAL_STORAGE_AUTO_PLAY_NEXT_VIDEO) === 'true'
|
||||
) {
|
||||
this.zone.run(() => this.autoplayNext())
|
||||
}
|
||||
|
@ -447,7 +451,10 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
|
|||
|
||||
this.player.one('stopped', () => {
|
||||
if (this.playlist) {
|
||||
this.zone.run(() => this.videoWatchPlaylist.navigateToNextPlaylistVideo())
|
||||
if (
|
||||
this.user && this.user.autoPlayNextVideoPlaylist ||
|
||||
peertubeLocalStorage.getItem(VideoWatchPlaylistComponent.LOCAL_STORAGE_AUTO_PLAY_NEXT_VIDEO_PLAYLIST) === 'true'
|
||||
) this.zone.run(() => this.videoWatchPlaylist.navigateToNextPlaylistVideo())
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ import { NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap'
|
|||
import { RecommendationsModule } from '@app/videos/recommendations/recommendations.module'
|
||||
import { VideoWatchPlaylistComponent } from '@app/videos/+video-watch/video-watch-playlist.component'
|
||||
import { QRCodeModule } from 'angularx-qrcode'
|
||||
import { InputSwitchModule } from 'primeng/inputswitch'
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
|
@ -19,7 +20,8 @@ import { QRCodeModule } from 'angularx-qrcode'
|
|||
SharedModule,
|
||||
NgbTooltipModule,
|
||||
QRCodeModule,
|
||||
RecommendationsModule
|
||||
RecommendationsModule,
|
||||
InputSwitchModule
|
||||
],
|
||||
|
||||
declarations: [
|
||||
|
|
|
@ -4,15 +4,17 @@
|
|||
<div i18n class="title-page title-page-single">
|
||||
Other videos
|
||||
</div>
|
||||
<div class="d-flex title-page-autoplay">
|
||||
<span>Autoplay</span>
|
||||
<div *ngIf="!playlist" class="d-flex title-page-autoplay">
|
||||
<span i18n>Autoplay</span>
|
||||
<p-inputSwitch [(ngModel)]="autoPlayNextVideo" (ngModelChange)="switchAutoPlayNextVideo()"></p-inputSwitch>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div *ngFor="let video of (videos$ | async)">
|
||||
<div *ngFor="let video of (videos$ | async); let i = index; let length = count">
|
||||
<my-video-miniature [video]="video" [user]="user" (videoBlacklisted)="onVideoRemoved()" (videoRemoved)="onVideoRemoved()">
|
||||
</my-video-miniature>
|
||||
|
||||
<hr *ngIf="!playlist && i == 0 && length > 1" />
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { Component, Input, Output, OnChanges, EventEmitter } from '@angular/core'
|
||||
import { Observable } from 'rxjs'
|
||||
import { Video } from '@app/shared/video/video.model'
|
||||
import { VideoPlaylist } from '@app/shared/video-playlist/video-playlist.model'
|
||||
import { RecommendationInfo } from '@app/shared/video/recommendation-info.model'
|
||||
import { RecommendedVideosStore } from '@app/videos/recommendations/recommended-videos.store'
|
||||
import { User } from '@app/shared'
|
||||
|
@ -14,10 +15,11 @@ import { peertubeLocalStorage } from '@app/shared/misc/peertube-local-storage'
|
|||
styleUrls: [ './recommended-videos.component.scss' ]
|
||||
})
|
||||
export class RecommendedVideosComponent implements OnChanges {
|
||||
private static LOCAL_STORAGE_AUTO_PLAY_NEXT_VIDEO = 'auto_play_next_video'
|
||||
static LOCAL_STORAGE_AUTO_PLAY_NEXT_VIDEO = 'auto_play_next_video'
|
||||
|
||||
@Input() inputRecommendation: RecommendationInfo
|
||||
@Input() user: User
|
||||
@Input() playlist: VideoPlaylist
|
||||
@Output() gotRecommendations = new EventEmitter<Video[]>()
|
||||
|
||||
readonly hasVideos$: Observable<boolean>
|
||||
|
|
|
@ -176,6 +176,7 @@ async function updateMe (req: express.Request, res: express.Response) {
|
|||
if (body.webTorrentEnabled !== undefined) user.webTorrentEnabled = body.webTorrentEnabled
|
||||
if (body.autoPlayVideo !== undefined) user.autoPlayVideo = body.autoPlayVideo
|
||||
if (body.autoPlayNextVideo !== undefined) user.autoPlayNextVideo = body.autoPlayNextVideo
|
||||
if (body.autoPlayNextVideoPlaylist !== undefined) user.autoPlayNextVideoPlaylist = body.autoPlayNextVideoPlaylist
|
||||
if (body.videosHistoryEnabled !== undefined) user.videosHistoryEnabled = body.videosHistoryEnabled
|
||||
if (body.videoLanguages !== undefined) user.videoLanguages = body.videoLanguages
|
||||
if (body.theme !== undefined) user.theme = body.theme
|
||||
|
|
|
@ -69,6 +69,10 @@ function isUserAutoPlayNextVideoValid (value: any) {
|
|||
return isBooleanValid(value)
|
||||
}
|
||||
|
||||
function isUserAutoPlayNextVideoPlaylistValid (value: any) {
|
||||
return isBooleanValid(value)
|
||||
}
|
||||
|
||||
function isNoInstanceConfigWarningModal (value: any) {
|
||||
return isBooleanValid(value)
|
||||
}
|
||||
|
@ -111,6 +115,7 @@ export {
|
|||
isUserWebTorrentEnabledValid,
|
||||
isUserAutoPlayVideoValid,
|
||||
isUserAutoPlayNextVideoValid,
|
||||
isUserAutoPlayNextVideoPlaylistValid,
|
||||
isUserDisplayNameValid,
|
||||
isUserDescriptionValid,
|
||||
isNoInstanceConfigWarningModal,
|
||||
|
|
|
@ -14,7 +14,7 @@ import { CONFIG, registerConfigChangedHandler } from './config'
|
|||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const LAST_MIGRATION_VERSION = 455
|
||||
const LAST_MIGRATION_VERSION = 460
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction,
|
||||
queryInterface: Sequelize.QueryInterface,
|
||||
sequelize: Sequelize.Sequelize,
|
||||
db: any
|
||||
}): Promise<void> {
|
||||
{
|
||||
const data = {
|
||||
type: Sequelize.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: true
|
||||
}
|
||||
|
||||
await utils.queryInterface.addColumn('user', 'autoPlayNextVideoPlaylist', data)
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
|
@ -26,6 +26,7 @@ import {
|
|||
isUserAdminFlagsValid,
|
||||
isUserAutoPlayVideoValid,
|
||||
isUserAutoPlayNextVideoValid,
|
||||
isUserAutoPlayNextVideoPlaylistValid,
|
||||
isUserBlockedReasonValid,
|
||||
isUserBlockedValid,
|
||||
isUserEmailVerifiedValid,
|
||||
|
@ -167,6 +168,12 @@ export class UserModel extends Model<UserModel> {
|
|||
@Column
|
||||
autoPlayNextVideo: boolean
|
||||
|
||||
@AllowNull(false)
|
||||
@Default(true)
|
||||
@Is('UserAutoPlayNextVideoPlaylist', value => throwIfNotValid(value, isUserAutoPlayNextVideoPlaylistValid, 'auto play next video for playlists boolean'))
|
||||
@Column
|
||||
autoPlayNextVideoPlaylist: boolean
|
||||
|
||||
@AllowNull(true)
|
||||
@Default(null)
|
||||
@Is('UserVideoLanguages', value => throwIfNotValid(value, isUserVideoLanguages, 'video languages'))
|
||||
|
@ -619,6 +626,7 @@ export class UserModel extends Model<UserModel> {
|
|||
videosHistoryEnabled: this.videosHistoryEnabled,
|
||||
autoPlayVideo: this.autoPlayVideo,
|
||||
autoPlayNextVideo: this.autoPlayNextVideo,
|
||||
autoPlayNextVideoPlaylist: this.autoPlayNextVideoPlaylist,
|
||||
videoLanguages: this.videoLanguages,
|
||||
|
||||
role: this.role,
|
||||
|
|
|
@ -8,6 +8,7 @@ export interface UserUpdateMe {
|
|||
webTorrentEnabled?: boolean
|
||||
autoPlayVideo?: boolean
|
||||
autoPlayNextVideo?: boolean
|
||||
autoPlayNextVideoPlaylist?: boolean
|
||||
videosHistoryEnabled?: boolean
|
||||
videoLanguages?: string[]
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ export interface User {
|
|||
|
||||
autoPlayVideo: boolean
|
||||
autoPlayNextVideo: boolean
|
||||
autoPlayNextVideoPlaylist: boolean
|
||||
webTorrentEnabled: boolean
|
||||
videosHistoryEnabled: boolean
|
||||
videoLanguages: string[]
|
||||
|
|
Loading…
Reference in New Issue