2022-03-04 13:40:02 +01:00
import { forkJoin , map , Observable , of , Subscription , switchMap } from 'rxjs'
2024-03-04 10:01:52 +01:00
import { PlatformLocation , NgClass , NgIf , NgTemplateOutlet } from '@angular/common'
2021-06-30 09:49:45 +02:00
import { Component , ElementRef , Inject , LOCALE_ID , NgZone , OnDestroy , OnInit , ViewChild } from '@angular/core'
2024-03-04 10:01:52 +01:00
import { ActivatedRoute , Router , RouterLink } from '@angular/router'
2020-09-25 10:04:21 +02:00
import {
AuthService ,
AuthUser ,
ConfirmService ,
Notifier ,
PeerTubeSocket ,
2021-05-15 06:30:24 +02:00
PluginService ,
2020-09-25 10:04:21 +02:00
RestExtractor ,
2021-02-02 10:37:52 +01:00
ScreenService ,
2020-09-25 10:04:21 +02:00
ServerService ,
2023-10-09 15:33:19 +02:00
Hotkey ,
HotkeysService ,
2021-12-15 15:58:10 +01:00
User ,
2023-11-08 16:03:49 +01:00
UserService ,
MetaService
2020-09-25 10:04:21 +02:00
} from '@app/core'
2020-06-23 14:10:17 +02:00
import { HooksService } from '@app/core/plugins/hooks.service'
2023-06-29 15:55:00 +02:00
import { isXPercentInViewport , scrollToTop , toBoolean } from '@app/helpers'
2023-07-31 14:34:36 +02:00
import { timeToInt } from '@peertube/peertube-core-utils'
2021-07-16 10:42:24 +02:00
import {
HTMLServerConfig ,
HttpStatusCode ,
2022-03-04 13:40:02 +01:00
LiveVideo ,
2021-07-16 10:42:24 +02:00
PeerTubeProblemDocument ,
ServerErrorCode ,
2023-06-01 14:51:16 +02:00
Storyboard ,
2021-07-16 10:42:24 +02:00
VideoCaption ,
2023-08-28 10:55:04 +02:00
VideoChapter ,
2021-07-16 10:42:24 +02:00
VideoPrivacy ,
2023-07-31 14:34:36 +02:00
VideoState ,
VideoStateType
} from '@peertube/peertube-models'
import { logger } from '@root-helpers/logger'
import { isP2PEnabled , videoRequiresFileToken , videoRequiresUserAuth } from '@root-helpers/video'
2019-02-06 10:39:50 +01:00
import {
2023-06-29 15:55:00 +02:00
HLSOptions ,
PeerTubePlayer ,
PeerTubePlayerContructorOptions ,
PeerTubePlayerLoadOptions ,
2020-06-23 14:10:17 +02:00
PlayerMode ,
videojs
2022-02-02 11:16:23 +01:00
} from '../../../assets/player'
import { cleanupVideoWatch , getStoredTheater , getStoredVideoWatchHistory } from '../../../assets/player/peertube-player-local-storage'
2020-06-23 14:10:17 +02:00
import { environment } from '../../../environments/environment'
2021-06-29 17:18:30 +02:00
import { VideoWatchPlaylistComponent } from './shared'
2024-03-04 10:01:52 +01:00
import { PlayerStylesComponent } from './player-styles.component'
import { PrivacyConcernsComponent } from './shared/information/privacy-concerns.component'
import { RecommendedVideosComponent } from './shared/recommendations/recommended-videos.component'
import { VideoCommentsComponent } from './shared/comment/video-comments.component'
import { VideoAttributesComponent } from './shared/metadata/video-attributes.component'
import { VideoDescriptionComponent } from './shared/metadata/video-description.component'
import { VideoAvatarChannelComponent } from './shared/metadata/video-avatar-channel.component'
import { ActionButtonsComponent } from './shared/action-buttons/action-buttons.component'
import { VideoViewsCounterComponent } from '../../shared/shared-video/video-views-counter.component'
import { DateToggleComponent } from '../../shared/shared-main/date/date-toggle.component'
import { VideoAlertComponent } from './shared/information/video-alert.component'
import { PluginPlaceholderComponent } from '../../shared/shared-main/plugins/plugin-placeholder.component'
2024-03-05 09:30:12 +01:00
import { VideoDetails } from '@app/shared/shared-main/video/video-details.model'
import { VideoCaptionService } from '@app/shared/shared-main/video-caption/video-caption.service'
import { VideoChapterService } from '@app/shared/shared-main/video/video-chapter.service'
import { VideoFileTokenService } from '@app/shared/shared-main/video/video-file-token.service'
import { VideoService } from '@app/shared/shared-main/video/video.service'
import { Video } from '@app/shared/shared-main/video/video.model'
import { VideoPlaylist } from '@app/shared/shared-video-playlist/video-playlist.model'
import { SubscribeButtonComponent } from '@app/shared/shared-user-subscription/subscribe-button.component'
import { LiveVideoService } from '@app/shared/shared-video-live/live-video.service'
import { VideoPlaylistService } from '@app/shared/shared-video-playlist/video-playlist.service'
2016-03-14 13:50:19 +01:00
2023-06-29 15:55:00 +02:00
type URLOptions = {
playerMode : PlayerMode
startTime : number | string
stopTime : number | string
controls? : boolean
controlBar? : boolean
muted? : boolean
loop? : boolean
subtitle? : string
resume? : string
peertubeLink : boolean
playbackRate? : number | string
}
2020-09-25 10:04:21 +02:00
2016-03-14 13:50:19 +01:00
@Component ( {
selector : 'my-video-watch' ,
2016-09-19 22:49:31 +02:00
templateUrl : './video-watch.component.html' ,
2024-03-04 10:01:52 +01:00
styleUrls : [ './video-watch.component.scss' ] ,
standalone : true ,
imports : [
NgClass ,
NgIf ,
VideoWatchPlaylistComponent ,
PluginPlaceholderComponent ,
VideoAlertComponent ,
DateToggleComponent ,
VideoViewsCounterComponent ,
NgTemplateOutlet ,
ActionButtonsComponent ,
VideoAvatarChannelComponent ,
RouterLink ,
SubscribeButtonComponent ,
VideoDescriptionComponent ,
VideoAttributesComponent ,
VideoCommentsComponent ,
RecommendedVideosComponent ,
PrivacyConcernsComponent ,
PlayerStylesComponent
]
2016-03-14 13:50:19 +01:00
} )
2016-07-08 17:15:14 +02:00
export class VideoWatchComponent implements OnInit , OnDestroy {
2019-07-24 16:05:59 +02:00
@ViewChild ( 'videoWatchPlaylist' , { static : true } ) videoWatchPlaylist : VideoWatchPlaylistComponent
2020-02-07 10:00:34 +01:00
@ViewChild ( 'subscribeButton' ) subscribeButton : SubscribeButtonComponent
2023-06-29 15:55:00 +02:00
@ViewChild ( 'playerElement' ) playerElement : ElementRef < HTMLVideoElement >
2017-06-16 14:32:15 +02:00
2023-06-29 15:55:00 +02:00
peertubePlayer : PeerTubePlayer
2021-06-30 09:49:45 +02:00
theaterEnabled = false
2017-10-30 20:26:06 +01:00
2019-06-12 12:40:24 +02:00
video : VideoDetails = null
videoCaptions : VideoCaption [ ] = [ ]
2023-08-28 10:55:04 +02:00
videoChapters : VideoChapter [ ] = [ ]
2022-03-04 13:40:02 +01:00
liveVideo : LiveVideo
2023-06-29 09:48:55 +02:00
videoPassword : string
2023-06-01 14:51:16 +02:00
storyboards : Storyboard [ ] = [ ]
2019-06-12 12:40:24 +02:00
2020-08-19 09:21:46 +02:00
playlistPosition : number
2019-03-13 14:18:58 +01:00
playlist : VideoPlaylist = null
2018-07-16 19:15:20 +02:00
remoteServerDown = false
2022-06-03 14:18:28 +02:00
noPlaylistVideoFound = false
2021-02-22 10:53:25 +01:00
2023-06-29 15:55:00 +02:00
private nextRecommendedVideoUUID = ''
private nextRecommendedVideoTitle = ''
2021-06-30 09:49:45 +02:00
2022-10-12 16:09:02 +02:00
private videoFileToken : string
2019-03-07 17:06:00 +01:00
private currentTime : number
2021-06-30 09:49:45 +02:00
2017-06-16 14:32:15 +02:00
private paramsSub : Subscription
2019-03-13 14:18:58 +01:00
private queryParamsSub : Subscription
2019-04-10 09:23:18 +02:00
private configSub : Subscription
2020-09-25 10:04:21 +02:00
private liveVideosSub : Subscription
2017-06-16 14:32:15 +02:00
2021-06-04 13:31:41 +02:00
private serverConfig : HTMLServerConfig
2019-12-18 15:31:54 +01:00
2021-06-29 17:00:30 +02:00
private hotkeys : Hotkey [ ] = [ ]
2017-06-16 14:32:15 +02:00
constructor (
2016-07-08 17:15:14 +02:00
private route : ActivatedRoute ,
2017-04-04 21:37:03 +02:00
private router : Router ,
2016-05-31 22:39:36 +02:00
private videoService : VideoService ,
2019-03-13 14:18:58 +01:00
private playlistService : VideoPlaylistService ,
2022-03-04 13:40:02 +01:00
private liveVideoService : LiveVideoService ,
2017-04-04 21:37:03 +02:00
private confirmService : ConfirmService ,
2017-01-27 16:14:11 +01:00
private authService : AuthService ,
2020-02-28 13:52:21 +01:00
private userService : UserService ,
2018-04-19 11:01:34 +02:00
private serverService : ServerService ,
2018-05-31 11:35:01 +02:00
private restExtractor : RestExtractor ,
2018-12-19 16:04:34 +01:00
private notifier : Notifier ,
2018-03-01 13:57:29 +01:00
private zone : NgZone ,
2018-07-13 18:21:19 +02:00
private videoCaptionService : VideoCaptionService ,
2023-08-28 10:55:04 +02:00
private videoChapterService : VideoChapterService ,
2018-09-02 20:54:23 +02:00
private hotkeysService : HotkeysService ,
2019-07-22 15:40:13 +02:00
private hooks : HooksService ,
2021-05-15 06:30:24 +02:00
private pluginService : PluginService ,
2020-09-25 10:04:21 +02:00
private peertubeSocket : PeerTubeSocket ,
2021-02-02 10:37:52 +01:00
private screenService : ScreenService ,
2022-10-12 16:09:02 +02:00
private videoFileTokenService : VideoFileTokenService ,
2019-08-22 17:13:58 +02:00
private location : PlatformLocation ,
2023-11-08 16:03:49 +01:00
private metaService : MetaService ,
2018-06-06 14:23:40 +02:00
@Inject ( LOCALE_ID ) private localeId : string
2021-02-02 10:37:52 +01:00
) { }
2016-03-14 13:50:19 +01:00
2017-12-12 14:41:59 +01:00
get user ( ) {
return this . authService . getUser ( )
}
2020-02-28 13:52:21 +01:00
get anonymousUser ( ) {
return this . userService . getAnonymousUser ( )
}
2023-06-29 15:55:00 +02:00
async ngOnInit ( ) {
2021-06-29 17:25:19 +02:00
this . serverConfig = this . serverService . getHTMLConfig ( )
2021-06-30 09:49:45 +02:00
this . loadRouteParams ( )
this . loadRouteQuery ( )
2018-09-02 20:54:23 +02:00
2019-06-11 16:26:48 +02:00
this . theaterEnabled = getStoredTheater ( )
2019-07-08 15:54:08 +02:00
2019-07-23 12:16:34 +02:00
this . hooks . runAction ( 'action:video-watch.init' , 'video-watch' )
2021-03-31 11:26:32 +02:00
setTimeout ( cleanupVideoWatch , 1500 ) // Run in timeout to ensure we're not blocking the UI
2023-06-29 15:55:00 +02:00
const constructorOptions = await this . hooks . wrapFun (
this . buildPeerTubePlayerConstructorOptions . bind ( this ) ,
{ urlOptions : this.getUrlOptions ( ) } ,
'video-watch' ,
'filter:internal.video-watch.player.build-options.params' ,
'filter:internal.video-watch.player.build-options.result'
)
this . peertubePlayer = new PeerTubePlayer ( constructorOptions )
2016-11-04 16:04:50 +01:00
}
2017-06-16 14:32:15 +02:00
ngOnDestroy ( ) {
2023-06-29 15:55:00 +02:00
if ( this . peertubePlayer ) this . peertubePlayer . destroy ( )
2016-11-08 21:17:17 +01:00
2017-01-29 18:35:19 +01:00
// Unsubscribe subscriptions
2019-03-13 14:18:58 +01:00
if ( this . paramsSub ) this . paramsSub . unsubscribe ( )
if ( this . queryParamsSub ) this . queryParamsSub . unsubscribe ( )
2020-08-04 11:42:06 +02:00
if ( this . configSub ) this . configSub . unsubscribe ( )
2020-09-25 10:04:21 +02:00
if ( this . liveVideosSub ) this . liveVideosSub . unsubscribe ( )
2018-09-02 20:54:23 +02:00
// Unbind hotkeys
2019-12-06 11:07:30 +01:00
this . hotkeysService . remove ( this . hotkeys )
2016-03-14 13:50:19 +01:00
}
2016-03-14 22:16:43 +01:00
2021-06-29 17:57:59 +02:00
getCurrentTime ( ) {
return this . currentTime
2020-08-03 21:06:45 +02:00
}
2021-06-29 17:57:59 +02:00
getCurrentPlaylistPosition ( ) {
return this . videoWatchPlaylist . currentPlaylistPosition
2016-11-08 21:11:57 +01:00
}
2019-09-24 08:48:01 +02:00
onRecommendations ( videos : Video [ ] ) {
2021-06-30 09:49:45 +02:00
if ( videos . length === 0 ) return
2019-09-24 08:48:01 +02:00
2021-06-30 09:49:45 +02:00
// The recommended videos's first element should be the next video
const video = videos [ 0 ]
2023-06-29 15:55:00 +02:00
this . nextRecommendedVideoUUID = video . uuid
this . nextRecommendedVideoTitle = video . name
2019-12-16 16:21:42 +01:00
}
handleTimestampClicked ( timestamp : number ) {
2023-06-29 15:55:00 +02:00
if ( ! this . peertubePlayer || this . video . isLive ) return
2020-12-17 14:14:28 +01:00
2023-06-29 15:55:00 +02:00
this . peertubePlayer . getPlayer ( ) . currentTime ( timestamp )
2019-12-16 16:21:42 +01:00
scrollToTop ( )
2019-12-12 18:11:55 +01:00
}
2021-06-30 09:49:45 +02:00
onPlaylistVideoFound ( videoId : string ) {
2022-11-15 11:57:49 +01:00
this . loadVideo ( { videoId , forceAutoplay : false } )
2021-06-30 09:49:45 +02:00
}
2022-06-03 14:18:28 +02:00
onPlaylistNoVideoFound ( ) {
this . noPlaylistVideoFound = true
}
2021-06-30 09:49:45 +02:00
isUserLoggedIn ( ) {
return this . authService . isLoggedIn ( )
}
2023-06-29 09:48:55 +02:00
isUserOwner ( ) {
return this . video . isLocal === true && this . video . account . name === this . user ? . username
}
2021-06-30 09:49:45 +02:00
isVideoBlur ( video : Video ) {
return video . isVideoNSFWForUser ( this . user , this . serverConfig )
2019-12-12 18:11:55 +01:00
}
2020-07-24 08:49:59 +02:00
isChannelDisplayNameGeneric ( ) {
const genericChannelDisplayName = [
` Main ${ this . video . channel . ownerAccount . name } channel ` ,
` Default ${ this . video . channel . ownerAccount . name } channel `
]
return genericChannelDisplayName . includes ( this . video . channel . displayName )
}
2021-04-01 11:10:27 +02:00
displayOtherVideosAsRow ( ) {
// Use the same value as in the SASS file
return this . screenService . getWindowInnerWidth ( ) <= 1100
}
2021-06-30 09:49:45 +02:00
private loadRouteParams ( ) {
this . paramsSub = this . route . params . subscribe ( routeParams = > {
2021-08-17 14:42:53 +02:00
const videoId = routeParams [ 'videoId' ]
2022-11-15 11:57:49 +01:00
if ( videoId ) return this . loadVideo ( { videoId , forceAutoplay : false } )
2021-06-30 09:49:45 +02:00
2021-08-17 14:42:53 +02:00
const playlistId = routeParams [ 'playlistId' ]
2021-06-30 09:49:45 +02:00
if ( playlistId ) return this . loadPlaylist ( playlistId )
} )
}
private loadRouteQuery ( ) {
this . queryParamsSub = this . route . queryParams . subscribe ( queryParams = > {
// Handle the ?playlistPosition
2024-03-19 11:29:04 +01:00
const positionParam = queryParams [ 'playlistPosition' ]
if ( ! positionParam ) return
2021-06-30 09:49:45 +02:00
this . playlistPosition = positionParam === 'last'
? - 1 // Handle the "last" index
: parseInt ( positionParam + '' , 10 )
if ( isNaN ( this . playlistPosition ) ) {
2022-07-15 15:30:14 +02:00
logger . error ( ` playlistPosition query param ' ${ positionParam } ' was parsed as NaN, defaulting to 1. ` )
2021-06-30 09:49:45 +02:00
this . playlistPosition = 1
}
this . videoWatchPlaylist . updatePlaylistIndex ( this . playlistPosition )
2021-08-17 14:42:53 +02:00
const start = queryParams [ 'start' ]
2024-03-19 11:29:04 +01:00
if ( this . peertubePlayer ? . getPlayer ( ) && start ) {
this . peertubePlayer . getPlayer ( ) . currentTime ( parseInt ( start , 10 ) )
}
2021-06-30 09:49:45 +02:00
} )
}
2022-11-15 11:57:49 +01:00
private loadVideo ( options : {
videoId : string
forceAutoplay : boolean
2023-06-29 09:48:55 +02:00
videoPassword? : string
2022-11-15 11:57:49 +01:00
} ) {
2023-06-29 09:48:55 +02:00
const { videoId , forceAutoplay , videoPassword } = options
2022-11-15 11:57:49 +01:00
2021-06-30 09:49:45 +02:00
if ( this . isSameElement ( this . video , videoId ) ) return
2019-03-13 14:18:58 +01:00
2022-07-29 10:32:56 +02:00
this . video = undefined
2019-07-22 15:40:13 +02:00
const videoObs = this . hooks . wrapObsFun (
this . videoService . getVideo . bind ( this . videoService ) ,
2023-06-29 09:48:55 +02:00
{ videoId , videoPassword } ,
2019-07-22 15:40:13 +02:00
'video-watch' ,
'filter:api.video-watch.video.get.params' ,
'filter:api.video-watch.video.get.result'
)
2022-10-12 16:09:02 +02:00
const videoAndLiveObs : Observable < { video : VideoDetails , live? : LiveVideo , videoFileToken? : string } > = videoObs . pipe (
2022-03-04 13:40:02 +01:00
switchMap ( video = > {
2022-10-12 16:09:02 +02:00
if ( ! video . isLive ) return of ( { video , live : undefined } )
2022-03-04 13:40:02 +01:00
return this . liveVideoService . getVideoLive ( video . uuid )
. pipe ( map ( live = > ( { live , video } ) ) )
2022-10-12 16:09:02 +02:00
} ) ,
switchMap ( ( { video , live } ) = > {
2023-06-29 09:48:55 +02:00
if ( ! videoRequiresFileToken ( video ) ) return of ( { video , live , videoFileToken : undefined } )
2022-10-12 16:09:02 +02:00
2023-06-29 09:48:55 +02:00
return this . videoFileTokenService . getVideoFileToken ( { videoUUID : video.uuid , videoPassword } )
2022-10-12 16:09:02 +02:00
. pipe ( map ( ( { token } ) = > ( { video , live , videoFileToken : token } ) ) )
2022-03-04 13:40:02 +01:00
} )
)
2021-12-15 15:58:10 +01:00
forkJoin ( [
2022-03-04 13:40:02 +01:00
videoAndLiveObs ,
2023-06-29 09:48:55 +02:00
this . videoCaptionService . listCaptions ( videoId , videoPassword ) ,
2023-08-28 10:55:04 +02:00
this . videoChapterService . getChapters ( { videoId , videoPassword } ) ,
2023-06-29 14:22:13 +02:00
this . videoService . getStoryboards ( videoId , videoPassword ) ,
2021-12-15 15:58:10 +01:00
this . userService . getAnonymousOrLoggedUser ( )
] ) . subscribe ( {
2023-08-28 10:55:04 +02:00
next : ( [ { video , live , videoFileToken } , captionsResult , chaptersResult , storyboards , loggedInOrAnonymousUser ] ) = > {
2022-11-15 11:57:49 +01:00
this . onVideoFetched ( {
video ,
live ,
videoCaptions : captionsResult.data ,
2023-08-28 10:55:04 +02:00
videoChapters : chaptersResult.chapters ,
2023-06-01 14:51:16 +02:00
storyboards ,
2022-11-15 11:57:49 +01:00
videoFileToken ,
2023-06-29 09:48:55 +02:00
videoPassword ,
2022-11-15 11:57:49 +01:00
loggedInOrAnonymousUser ,
forceAutoplay
2023-06-29 09:48:55 +02:00
} ) . catch ( err = > {
this . handleGlobalError ( err )
} )
2021-12-15 15:58:10 +01:00
} ,
2023-06-29 09:48:55 +02:00
error : async err = > {
if ( err . body . code === ServerErrorCode . VIDEO_REQUIRES_PASSWORD || err . body . code === ServerErrorCode . INCORRECT_VIDEO_PASSWORD ) {
const { confirmed , password } = await this . handleVideoPasswordError ( err )
if ( confirmed === false ) return this . location . back ( )
2019-05-31 11:48:28 +02:00
2023-06-29 09:48:55 +02:00
this . loadVideo ( { . . . options , videoPassword : password } )
} else {
this . handleRequestError ( err )
}
}
2021-12-15 15:58:10 +01:00
} )
2019-03-13 14:18:58 +01:00
}
private loadPlaylist ( playlistId : string ) {
2021-06-30 09:49:45 +02:00
if ( this . isSameElement ( this . playlist , playlistId ) ) return
2019-03-13 14:18:58 +01:00
2022-06-03 14:18:28 +02:00
this . noPlaylistVideoFound = false
2019-03-13 14:18:58 +01:00
this . playlistService . getVideoPlaylist ( playlistId )
2021-08-17 11:27:47 +02:00
. subscribe ( {
next : playlist = > {
2021-06-30 09:49:45 +02:00
this . playlist = playlist
this . videoWatchPlaylist . loadPlaylistElements ( playlist , ! this . playlistPosition , this . playlistPosition )
} ,
2021-08-17 11:27:47 +02:00
error : err = > this . handleRequestError ( err )
} )
2021-06-30 09:49:45 +02:00
}
2019-03-13 14:18:58 +01:00
2021-06-30 09:49:45 +02:00
private isSameElement ( element : VideoDetails | VideoPlaylist , newId : string ) {
if ( ! element ) return false
return ( element . id + '' ) === newId || element . uuid === newId || element . shortUUID === newId
}
private async handleRequestError ( err : any ) {
const errorBody = err . body as PeerTubeProblemDocument
2021-11-02 11:50:03 +01:00
if ( errorBody ? . code === ServerErrorCode . DOES_NOT_RESPECT_FOLLOW_CONSTRAINTS && errorBody . originUrl ) {
2021-06-30 09:49:45 +02:00
const originUrl = errorBody . originUrl + ( window . location . search ? ? '' )
const res = await this . confirmService . confirm (
2021-08-17 14:42:53 +02:00
// eslint-disable-next-line max-len
2021-06-30 09:49:45 +02:00
$localize ` This video is not available on this instance. Do you want to be redirected on the origin instance: <a href=" ${ originUrl } "> ${ originUrl } </a>? ` ,
$localize ` Redirection `
)
if ( res === true ) return window . location . href = originUrl
}
// If 400, 403 or 404, the video is private or blocked so redirect to 404
return this . restExtractor . redirectTo404IfNotFound ( err , 'video' , [
HttpStatusCode . BAD_REQUEST_400 ,
HttpStatusCode . FORBIDDEN_403 ,
HttpStatusCode . NOT_FOUND_404
] )
2019-03-13 14:18:58 +01:00
}
2021-06-30 09:49:45 +02:00
private handleGlobalError ( err : any ) {
2017-07-23 11:07:30 +02:00
const errorMessage : string = typeof err === 'string' ? err : err.message
2018-02-26 09:55:23 +01:00
if ( ! errorMessage ) return
2018-12-19 16:04:34 +01:00
this . notifier . error ( errorMessage )
2017-07-23 11:07:30 +02:00
}
2023-06-29 09:48:55 +02:00
private handleVideoPasswordError ( err : any ) {
let isIncorrectPassword : boolean
if ( err . body . code === ServerErrorCode . VIDEO_REQUIRES_PASSWORD ) {
isIncorrectPassword = false
} else if ( err . body . code === ServerErrorCode . INCORRECT_VIDEO_PASSWORD ) {
this . videoPassword = undefined
isIncorrectPassword = true
}
return this . confirmService . confirmWithPassword ( {
message : $localize ` You need a password to watch this video ` ,
title : $localize ` This video is password protected ` ,
errorMessage : isIncorrectPassword ? $localize ` Incorrect password, please enter a correct password ` : ''
} )
}
2021-12-15 15:58:10 +01:00
private async onVideoFetched ( options : {
video : VideoDetails
2022-03-04 13:40:02 +01:00
live : LiveVideo
2021-12-15 15:58:10 +01:00
videoCaptions : VideoCaption [ ]
2023-08-28 10:55:04 +02:00
videoChapters : VideoChapter [ ]
2023-06-01 14:51:16 +02:00
storyboards : Storyboard [ ]
2022-10-12 16:09:02 +02:00
videoFileToken : string
2023-06-29 09:48:55 +02:00
videoPassword : string
2022-10-12 16:09:02 +02:00
2021-12-15 15:58:10 +01:00
loggedInOrAnonymousUser : User
2022-11-15 11:57:49 +01:00
forceAutoplay : boolean
2021-12-15 15:58:10 +01:00
} ) {
2023-06-01 14:51:16 +02:00
const {
video ,
live ,
videoCaptions ,
2023-08-28 10:55:04 +02:00
videoChapters ,
2023-06-01 14:51:16 +02:00
storyboards ,
videoFileToken ,
videoPassword ,
loggedInOrAnonymousUser ,
forceAutoplay
} = options
2021-12-15 15:58:10 +01:00
2020-09-25 10:04:21 +02:00
this . subscribeToLiveEventsIfNeeded ( this . video , video )
2017-06-16 14:32:15 +02:00
this . video = video
2019-06-12 12:40:24 +02:00
this . videoCaptions = videoCaptions
2023-08-28 10:55:04 +02:00
this . videoChapters = videoChapters
2022-03-04 13:40:02 +01:00
this . liveVideo = live
2022-10-12 16:09:02 +02:00
this . videoFileToken = videoFileToken
2023-06-29 09:48:55 +02:00
this . videoPassword = videoPassword
2023-06-01 14:51:16 +02:00
this . storyboards = storyboards
2017-04-04 21:37:03 +02:00
2018-04-04 09:04:34 +02:00
// Re init attributes
2018-07-16 19:15:20 +02:00
this . remoteServerDown = false
2019-03-07 17:06:00 +01:00
this . currentTime = undefined
2018-04-04 09:04:34 +02:00
2019-03-13 14:18:58 +01:00
if ( this . isVideoBlur ( this . video ) ) {
2018-02-28 15:33:45 +01:00
const res = await this . confirmService . confirm (
2020-08-12 10:40:04 +02:00
$localize ` This video contains mature or explicit content. Are you sure you want to watch it? ` ,
$localize ` Mature or explicit content `
2017-10-27 08:51:40 +02:00
)
2019-08-22 17:13:58 +02:00
if ( res === false ) return this . location . back ( )
2017-04-04 21:37:03 +02:00
}
2022-12-30 15:54:08 +01:00
this . buildHotkeysHelp ( video )
2023-11-08 16:03:49 +01:00
this . setMetaTags ( video )
2022-12-30 15:54:08 +01:00
2023-06-29 15:55:00 +02:00
this . loadPlayer ( { loggedInOrAnonymousUser , forceAutoplay } )
2022-07-15 15:30:14 +02:00
. catch ( err = > logger . error ( 'Cannot build the player' , err ) )
2021-02-22 10:46:52 +01:00
2021-04-09 10:54:34 +02:00
const hookOptions = {
videojs ,
video : this.video ,
playlist : this.playlist
}
this . hooks . runAction ( 'action:video-watch.video.loaded' , 'video-watch' , hookOptions )
2021-02-22 10:46:52 +01:00
}
2023-06-29 15:55:00 +02:00
private async loadPlayer ( options : {
2022-11-15 11:57:49 +01:00
loggedInOrAnonymousUser : User
forceAutoplay : boolean
} ) {
2023-06-29 15:55:00 +02:00
const { loggedInOrAnonymousUser , forceAutoplay } = options
2018-04-03 17:33:39 +02:00
2021-02-22 10:53:25 +01:00
const videoState = this . video . state . id
if ( videoState === VideoState . LIVE_ENDED || videoState === VideoState . WAITING_FOR_LIVE ) {
2023-06-29 15:55:00 +02:00
this . updatePlayerOnNoLive ( )
2021-02-22 10:53:25 +01:00
return
}
2023-06-29 15:55:00 +02:00
this . peertubePlayer ? . enable ( )
2018-04-03 17:33:39 +02:00
2019-12-05 17:06:18 +01:00
const params = {
video : this.video ,
2021-02-22 10:46:52 +01:00
videoCaptions : this.videoCaptions ,
2023-08-28 10:55:04 +02:00
videoChapters : this.videoChapters ,
2023-06-01 14:51:16 +02:00
storyboards : this.storyboards ,
2022-03-04 13:40:02 +01:00
liveVideo : this.liveVideo ,
2022-10-12 16:09:02 +02:00
videoFileToken : this.videoFileToken ,
2023-06-29 09:48:55 +02:00
videoPassword : this.videoPassword ,
2023-06-29 15:55:00 +02:00
urlOptions : this.getUrlOptions ( ) ,
2021-12-15 15:58:10 +01:00
loggedInOrAnonymousUser ,
2022-11-15 11:57:49 +01:00
forceAutoplay ,
2019-12-05 17:06:18 +01:00
user : this.user
2018-06-06 14:23:40 +02:00
}
2023-06-29 15:55:00 +02:00
const loadOptions = await this . hooks . wrapFun (
this . buildPeerTubePlayerLoadOptions . bind ( this ) ,
2019-12-05 17:06:18 +01:00
params ,
2019-12-05 17:26:58 +01:00
'video-watch' ,
2023-06-29 15:55:00 +02:00
'filter:internal.video-watch.player.load-options.params' ,
'filter:internal.video-watch.player.load-options.result'
2019-12-05 17:06:18 +01:00
)
2018-06-06 14:23:40 +02:00
this . zone . runOutsideAngular ( async ( ) = > {
2023-06-29 15:55:00 +02:00
await this . peertubePlayer . load ( loadOptions )
2019-03-18 10:26:53 +01:00
2023-06-29 15:55:00 +02:00
const player = this . peertubePlayer . getPlayer ( )
2019-03-07 17:06:00 +01:00
2023-06-29 15:55:00 +02:00
player . on ( 'timeupdate' , ( ) = > {
2021-06-30 09:49:45 +02:00
// Don't need to trigger angular change for this variable, that is sent to children components on click
2023-06-29 15:55:00 +02:00
this . currentTime = Math . floor ( player . currentTime ( ) )
2019-03-07 17:06:00 +01:00
} )
2019-03-13 14:18:58 +01:00
2023-06-29 15:55:00 +02:00
if ( this . video . isLive ) {
player . one ( 'ended' , ( ) = > {
this . zone . run ( ( ) = > {
// We changed the video, it's not a live anymore
if ( ! this . video . isLive ) return
2021-06-30 09:49:45 +02:00
2023-06-29 15:55:00 +02:00
this . video . state . id = VideoState . LIVE_ENDED
2021-06-30 09:49:45 +02:00
2023-06-29 15:55:00 +02:00
this . updatePlayerOnNoLive ( )
} )
} )
}
2020-12-04 15:29:18 +01:00
2023-06-29 15:55:00 +02:00
player . on ( 'theater-change' , ( _ : any , enabled : boolean ) = > {
2019-03-18 10:26:53 +01:00
this . zone . run ( ( ) = > this . theaterEnabled = enabled )
} )
2019-11-18 09:55:23 +01:00
2021-10-12 13:45:55 +02:00
this . hooks . runAction ( 'action:video-watch.player.loaded' , 'video-watch' , {
2023-06-29 15:55:00 +02:00
player ,
2021-10-12 13:45:55 +02:00
playlist : this.playlist ,
playlistPosition : this.playlistPosition ,
videojs ,
video : this.video
} )
2018-04-03 17:33:39 +02:00
} )
2017-04-04 21:37:03 +02:00
}
2022-01-07 14:25:23 +01:00
private hasNextVideo ( ) {
if ( this . playlist ) {
return this . videoWatchPlaylist . hasNextVideo ( )
}
return true
}
2023-06-29 15:55:00 +02:00
private getNextVideoTitle ( ) {
2020-03-17 15:05:28 +01:00
if ( this . playlist ) {
2023-06-29 15:55:00 +02:00
return this . videoWatchPlaylist . getNextVideo ( ) ? . video ? . name || ''
2019-09-24 08:48:01 +02:00
}
2016-11-04 17:37:44 +01:00
2023-06-29 15:55:00 +02:00
return this . nextRecommendedVideoTitle
}
private playNextVideoInAngularZone ( ) {
this . zone . run ( ( ) = > {
if ( this . playlist ) {
this . videoWatchPlaylist . navigateToNextPlaylistVideo ( )
return
}
if ( this . nextRecommendedVideoUUID ) {
this . router . navigate ( [ '/w' , this . nextRecommendedVideoUUID ] )
}
} )
2016-11-04 17:37:44 +01:00
}
2017-11-30 09:21:11 +01:00
2017-12-19 14:01:34 +01:00
private isAutoplay ( ) {
2018-06-14 11:25:49 +02:00
// We'll jump to the thread id, so do not play the video
if ( this . route . snapshot . params [ 'threadId' ] ) return false
2023-07-13 14:40:06 +02:00
if ( this . user ) return this . user . autoPlayVideo
2017-12-19 14:01:34 +01:00
2023-07-13 14:40:06 +02:00
if ( this . anonymousUser ) return this . anonymousUser . autoPlayVideo
throw new Error ( 'Cannot guess autoplay because user and anonymousUser are not defined' )
2017-12-19 14:01:34 +01:00
}
2018-04-03 18:06:58 +02:00
2021-06-30 09:49:45 +02:00
private isAutoPlayNext ( ) {
return (
2021-08-17 14:42:53 +02:00
( this . user ? . autoPlayNextVideo ) ||
2021-06-30 09:49:45 +02:00
this . anonymousUser . autoPlayNextVideo
)
}
private isPlaylistAutoPlayNext ( ) {
return (
2021-08-17 14:42:53 +02:00
( this . user ? . autoPlayNextVideoPlaylist ) ||
2021-06-30 09:49:45 +02:00
this . anonymousUser . autoPlayNextVideoPlaylist
)
}
2023-06-29 15:55:00 +02:00
private buildPeerTubePlayerConstructorOptions ( options : {
urlOptions : URLOptions
} ) : PeerTubePlayerContructorOptions {
const { urlOptions } = options
return {
playerElement : ( ) = > this . playerElement . nativeElement ,
enableHotkeys : true ,
inactivityTimeout : 2500 ,
theaterButton : true ,
controls : urlOptions.controls ,
controlBar : urlOptions.controlBar ,
muted : urlOptions.muted ,
loop : urlOptions.loop ,
playbackRate : urlOptions.playbackRate ,
instanceName : this.serverConfig.instance.name ,
language : this.localeId ,
2023-12-13 10:06:25 +01:00
metricsUrl : this.serverConfig.openTelemetry.metrics.enabled
? environment . apiUrl + '/api/v1/metrics/playback'
: null ,
metricsInterval : this.serverConfig.openTelemetry.metrics.playbackStatsInterval ,
videoViewIntervalMs : this.isUserLoggedIn ( )
? this . serverConfig . views . videos . watchingInterval . users
: this . serverConfig . views . videos . watchingInterval . anonymous ,
2023-06-29 15:55:00 +02:00
authorizationHeader : ( ) = > this . authService . getRequestHeaderValue ( ) ,
serverUrl : environment.originServerUrl || window . location . origin ,
2021-06-28 17:30:59 +02:00
2023-06-29 15:55:00 +02:00
errorNotifier : ( message : string ) = > this . notifier . error ( message ) ,
peertubeLink : ( ) = > false ,
2023-08-18 09:48:45 +02:00
pluginsManager : this.pluginService.getPluginsManager ( ) ,
autoPlayerRatio : {
cssRatioVariable : '--player-ratio' ,
cssPlayerPortraitModeVariable : '--player-portrait-mode'
}
2018-04-03 18:06:58 +02:00
}
}
2019-05-17 14:34:21 +02:00
2023-06-29 15:55:00 +02:00
private buildPeerTubePlayerLoadOptions ( options : {
2021-08-17 14:42:53 +02:00
video : VideoDetails
2022-03-04 13:40:02 +01:00
liveVideo : LiveVideo
2021-08-17 14:42:53 +02:00
videoCaptions : VideoCaption [ ]
2023-08-28 10:55:04 +02:00
videoChapters : VideoChapter [ ]
2023-06-01 14:51:16 +02:00
storyboards : Storyboard [ ]
2022-10-12 16:09:02 +02:00
videoFileToken : string
2023-06-29 09:48:55 +02:00
videoPassword : string
2022-10-12 16:09:02 +02:00
2023-06-29 15:55:00 +02:00
urlOptions : URLOptions
2022-10-12 16:09:02 +02:00
2021-12-15 15:58:10 +01:00
loggedInOrAnonymousUser : User
2022-11-15 11:57:49 +01:00
forceAutoplay : boolean
2022-04-05 14:03:52 +02:00
user? : AuthUser // Keep for plugins
2023-06-29 15:55:00 +02:00
} ) : PeerTubePlayerLoadOptions {
2023-06-01 14:51:16 +02:00
const {
video ,
liveVideo ,
videoCaptions ,
2023-08-28 10:55:04 +02:00
videoChapters ,
2023-06-01 14:51:16 +02:00
storyboards ,
videoFileToken ,
videoPassword ,
urlOptions ,
loggedInOrAnonymousUser ,
forceAutoplay
2023-06-29 15:55:00 +02:00
} = options
let mode : PlayerMode
if ( urlOptions . playerMode ) {
if ( urlOptions . playerMode === 'p2p-media-loader' ) mode = 'p2p-media-loader'
else mode = 'web-video'
} else {
if ( video . hasHlsPlaylist ( ) ) mode = 'p2p-media-loader'
else mode = 'web-video'
}
let hlsOptions : HLSOptions
if ( video . hasHlsPlaylist ( ) ) {
const hlsPlaylist = video . getHlsPlaylist ( )
hlsOptions = {
playlistUrl : hlsPlaylist.playlistUrl ,
segmentsSha256Url : hlsPlaylist.segmentsSha256Url ,
redundancyBaseUrls : hlsPlaylist.redundancies.map ( r = > r . baseUrl ) ,
trackerAnnounce : video.trackerUrls ,
videoFiles : hlsPlaylist.files
}
}
2021-06-30 09:49:45 +02:00
2019-12-12 18:11:55 +01:00
const getStartTime = ( ) = > {
2023-10-30 11:04:26 +01:00
if ( video . isLive ) return undefined
2019-12-12 18:11:55 +01:00
const byUrl = urlOptions . startTime !== undefined
2019-12-18 23:39:07 +01:00
const byHistory = video . userHistory && ( ! this . playlist || urlOptions . resume !== undefined )
2021-03-31 11:26:32 +02:00
const byLocalStorage = getStoredVideoWatchHistory ( video . uuid )
2019-12-12 18:11:55 +01:00
2020-08-18 16:04:03 +02:00
if ( byUrl ) return timeToInt ( urlOptions . startTime )
2023-02-25 16:18:28 +01:00
let startTime = 0
if ( byHistory ) startTime = video . userHistory . currentTime
if ( byLocalStorage ) startTime = byLocalStorage . duration
2019-12-05 17:06:18 +01:00
2023-02-25 16:18:28 +01:00
// If we are at the end of the video, reset the timer
if ( video . duration - startTime <= 1 ) startTime = 0
return startTime
}
2020-08-18 16:04:03 +02:00
2023-02-25 16:18:28 +01:00
const startTime = getStartTime ( )
2019-12-05 17:06:18 +01:00
const playerCaptions = videoCaptions . map ( c = > ( {
label : c.language.label ,
language : c.language.id ,
src : environment.apiUrl + c . captionPath
} ) )
2023-06-01 14:51:16 +02:00
const storyboard = storyboards . length !== 0
? {
url : environment.apiUrl + storyboards [ 0 ] . storyboardPath ,
height : storyboards [ 0 ] . spriteHeight ,
width : storyboards [ 0 ] . spriteWidth ,
interval : storyboards [ 0 ] . spriteDuration
}
: undefined
2022-03-04 13:40:02 +01:00
const liveOptions = video . isLive
? { latencyMode : liveVideo.latencyMode }
: undefined
2023-06-29 15:55:00 +02:00
return {
mode ,
2019-12-05 17:06:18 +01:00
2023-06-29 15:55:00 +02:00
autoplay : this.isAutoplay ( ) ,
forceAutoplay ,
2019-12-05 17:06:18 +01:00
2023-06-29 15:55:00 +02:00
duration : this.video.duration ,
poster : video.previewUrl ,
p2pEnabled : isP2PEnabled ( video , this . serverConfig , loggedInOrAnonymousUser . p2pEnabled ) ,
2019-12-05 17:06:18 +01:00
2023-06-29 15:55:00 +02:00
startTime ,
stopTime : urlOptions.stopTime ,
2019-12-05 17:06:18 +01:00
2024-04-11 08:39:11 +02:00
subtitle : urlOptions.subtitle ,
2023-06-29 15:55:00 +02:00
embedUrl : video.embedUrl ,
embedTitle : video.name ,
2020-11-10 14:21:26 +01:00
2023-06-29 15:55:00 +02:00
isLive : video.isLive ,
liveOptions ,
2019-12-05 17:06:18 +01:00
2023-06-29 15:55:00 +02:00
videoViewUrl : video.privacy.id !== VideoPrivacy . PRIVATE
? this . videoService . getVideoViewUrl ( video . uuid )
: null ,
2022-10-12 16:09:02 +02:00
2023-06-29 15:55:00 +02:00
videoFileToken : ( ) = > videoFileToken ,
requiresUserAuth : videoRequiresUserAuth ( video , videoPassword ) ,
requiresPassword : video.privacy.id === VideoPrivacy . PASSWORD_PROTECTED &&
! video . canAccessPasswordProtectedVideoWithoutPassword ( this . user ) ,
videoPassword : ( ) = > videoPassword ,
2022-10-12 16:09:02 +02:00
2023-06-29 15:55:00 +02:00
videoCaptions : playerCaptions ,
2023-08-28 10:55:04 +02:00
videoChapters ,
2023-06-29 15:55:00 +02:00
storyboard ,
2022-10-12 16:09:02 +02:00
2023-06-29 15:55:00 +02:00
videoShortUUID : video.shortUUID ,
videoUUID : video.uuid ,
2019-12-05 17:06:18 +01:00
2024-02-27 16:24:48 +01:00
videoRatio : video.aspectRatio ,
2023-06-29 15:55:00 +02:00
previousVideo : {
enabled : this.playlist && this . videoWatchPlaylist . hasPreviousVideo ( ) ,
2021-03-31 11:26:32 +02:00
2023-06-29 15:55:00 +02:00
handler : this.playlist
? ( ) = > this . zone . run ( ( ) = > this . videoWatchPlaylist . navigateToPreviousPlaylistVideo ( ) )
: undefined ,
2022-02-02 11:16:23 +01:00
2023-06-29 15:55:00 +02:00
displayControlBarButton : ! ! this . playlist
2019-12-05 17:06:18 +01:00
} ,
2023-06-29 15:55:00 +02:00
nextVideo : {
enabled : this.hasNextVideo ( ) ,
handler : ( ) = > this . playNextVideoInAngularZone ( ) ,
getVideoTitle : ( ) = > this . getNextVideoTitle ( ) ,
displayControlBarButton : this.hasNextVideo ( )
2021-05-15 06:30:24 +02:00
} ,
2023-06-29 15:55:00 +02:00
upnext : {
isEnabled : ( ) = > {
if ( this . playlist ) return this . isPlaylistAutoPlayNext ( )
2019-12-05 17:06:18 +01:00
2023-06-29 15:55:00 +02:00
return this . isAutoPlayNext ( )
} ,
2019-12-05 17:06:18 +01:00
2023-06-29 15:55:00 +02:00
isSuspended : ( player : videojs.Player ) = > {
return ! isXPercentInViewport ( player . el ( ) as HTMLElement , 80 )
} ,
2020-05-13 10:39:54 +02:00
2023-06-29 15:55:00 +02:00
timeout : this.playlist
? 0 // Don't wait to play next video in playlist
: 5000 // 5 seconds for a recommended video
} ,
2019-12-05 17:06:18 +01:00
2023-06-29 15:55:00 +02:00
hls : hlsOptions ,
2019-12-05 17:06:18 +01:00
2023-06-29 15:55:00 +02:00
webVideo : {
videoFiles : video.files
}
2019-12-05 17:06:18 +01:00
}
}
2020-09-25 10:04:21 +02:00
private async subscribeToLiveEventsIfNeeded ( oldVideo : VideoDetails , newVideo : VideoDetails ) {
if ( ! this . liveVideosSub ) {
2020-12-09 15:00:02 +01:00
this . liveVideosSub = this . buildLiveEventsSubscription ( )
2020-09-25 10:04:21 +02:00
}
if ( oldVideo && oldVideo . id !== newVideo . id ) {
2021-08-25 16:14:11 +02:00
this . peertubeSocket . unsubscribeLiveVideos ( oldVideo . id )
2020-09-25 10:04:21 +02:00
}
if ( ! newVideo . isLive ) return
await this . peertubeSocket . subscribeToLiveVideosSocket ( newVideo . id )
}
2020-12-09 15:00:02 +01:00
private buildLiveEventsSubscription ( ) {
return this . peertubeSocket . getLiveVideosObservable ( )
. subscribe ( ( { type , payload } ) = > {
if ( type === 'state-change' ) return this . handleLiveStateChange ( payload . state )
2021-11-09 10:11:20 +01:00
if ( type === 'views-change' ) return this . handleLiveViewsChange ( payload . viewers )
2020-12-09 15:00:02 +01:00
} )
}
2023-07-31 14:34:36 +02:00
private handleLiveStateChange ( newState : VideoStateType ) {
2020-12-09 15:00:02 +01:00
if ( newState !== VideoState . PUBLISHED ) return
2022-07-15 15:30:14 +02:00
logger . info ( 'Loading video after live update.' )
2020-12-09 15:00:02 +01:00
const videoUUID = this . video . uuid
2021-06-30 09:49:45 +02:00
// Reset to force refresh the video
2020-12-09 15:00:02 +01:00
this . video = undefined
2022-11-15 11:57:49 +01:00
this . loadVideo ( { videoId : videoUUID , forceAutoplay : true } )
2020-12-09 15:00:02 +01:00
}
2021-11-09 10:11:20 +01:00
private handleLiveViewsChange ( newViewers : number ) {
2020-12-09 15:00:02 +01:00
if ( ! this . video ) {
2022-07-15 15:30:14 +02:00
logger . error ( 'Cannot update video live views because video is no defined.' )
2020-12-09 15:00:02 +01:00
return
}
2022-07-15 15:30:14 +02:00
logger . info ( 'Updating live views.' )
2020-12-10 09:37:53 +01:00
2021-11-09 10:11:20 +01:00
this . video . viewers = newViewers
2020-12-09 15:00:02 +01:00
}
2023-06-29 15:55:00 +02:00
private updatePlayerOnNoLive ( ) {
this . peertubePlayer . unload ( )
this . peertubePlayer . disable ( )
this . peertubePlayer . setPoster ( this . video . previewPath )
}
2022-12-30 15:54:08 +01:00
private buildHotkeysHelp ( video : Video ) {
if ( this . hotkeys . length !== 0 ) {
this . hotkeysService . remove ( this . hotkeys )
}
2019-12-06 09:55:36 +01:00
this . hotkeys = [
// These hotkeys are managed by the player
2023-10-09 15:33:19 +02:00
new Hotkey ( 'f' , e = > e , $localize ` Enter/exit fullscreen ` ) ,
new Hotkey ( 'space' , e = > e , $localize ` Play/Pause the video ` ) ,
new Hotkey ( 'm' , e = > e , $localize ` Mute/unmute the video ` ) ,
2019-12-06 09:55:36 +01:00
2023-10-09 15:33:19 +02:00
new Hotkey ( 'up' , e = > e , $localize ` Increase the volume ` ) ,
new Hotkey ( 'down' , e = > e , $localize ` Decrease the volume ` ) ,
2019-12-06 09:55:36 +01:00
2022-01-12 16:01:39 +01:00
new Hotkey ( 't' , e = > {
this . theaterEnabled = ! this . theaterEnabled
return false
2023-10-09 15:33:19 +02:00
} , $localize ` Toggle theater mode ` )
2019-12-06 09:55:36 +01:00
]
2019-12-06 11:07:30 +01:00
2022-12-30 15:54:08 +01:00
if ( ! video . isLive ) {
this . hotkeys = this . hotkeys . concat ( [
// These hotkeys are also managed by the player but only for VOD
2023-10-09 15:33:19 +02:00
new Hotkey ( '0-9' , e = > e , $localize ` Skip to a percentage of the video: 0 is 0% and 9 is 90% ` ) ,
2022-12-30 15:54:08 +01:00
2023-10-09 15:33:19 +02:00
new Hotkey ( 'right' , e = > e , $localize ` Seek the video forward ` ) ,
new Hotkey ( 'left' , e = > e , $localize ` Seek the video backward ` ) ,
2022-12-30 15:54:08 +01:00
2023-10-09 15:33:19 +02:00
new Hotkey ( '>' , e = > e , $localize ` Increase playback rate ` ) ,
new Hotkey ( '<' , e = > e , $localize ` Decrease playback rate ` ) ,
2022-12-30 15:54:08 +01:00
2023-10-09 15:33:19 +02:00
new Hotkey ( ',' , e = > e , $localize ` Navigate in the video to the previous frame ` ) ,
new Hotkey ( '.' , e = > e , $localize ` Navigate in the video to the next frame ` )
2022-12-30 15:54:08 +01:00
] )
}
2019-12-06 11:07:30 +01:00
if ( this . isUserLoggedIn ( ) ) {
this . hotkeys = this . hotkeys . concat ( [
new Hotkey ( 'shift+s' , ( ) = > {
2021-08-17 14:42:53 +02:00
if ( this . subscribeButton . isSubscribedToAll ( ) ) this . subscribeButton . unsubscribe ( )
else this . subscribeButton . subscribe ( )
2021-07-12 10:03:46 +02:00
2019-12-06 11:07:30 +01:00
return false
2023-10-09 15:33:19 +02:00
} , $localize ` Subscribe to the account ` )
2019-12-06 11:07:30 +01:00
] )
}
this . hotkeysService . add ( this . hotkeys )
2019-12-06 09:55:36 +01:00
}
2021-06-30 09:49:45 +02:00
2023-11-08 16:03:49 +01:00
private setMetaTags ( video : Video ) {
this . metaService . setTitle ( video . name )
this . metaService . setTag ( 'description' , video . description )
}
2023-06-29 15:55:00 +02:00
private getUrlOptions ( ) : URLOptions {
const queryParams = this . route . snapshot . queryParams
return {
resume : queryParams.resume ,
startTime : queryParams.start ,
stopTime : queryParams.stop ,
muted : toBoolean ( queryParams . muted ) ,
loop : toBoolean ( queryParams . loop ) ,
subtitle : queryParams.subtitle ,
playerMode : queryParams.mode ,
playbackRate : queryParams.playbackRate ,
controlBar : toBoolean ( queryParams . controlBar ) ,
peertubeLink : false
}
}
2016-03-14 13:50:19 +01:00
}