diff options
author | Chocobozzz <me@florianbigard.com> | 2018-07-13 18:21:19 +0200 |
---|---|---|
committer | Chocobozzz <me@florianbigard.com> | 2018-07-16 11:50:08 +0200 |
commit | 16f7022b06fb76c0b00c23c970bc8df605b0ec63 (patch) | |
tree | 0677c72b449485dcaa87ee2b470dfb1a8124b9e0 /client/src | |
parent | 40e87e9ecc54e3513fb586928330a7855eb192c6 (diff) | |
download | PeerTube-16f7022b06fb76c0b00c23c970bc8df605b0ec63.tar.gz PeerTube-16f7022b06fb76c0b00c23c970bc8df605b0ec63.tar.zst PeerTube-16f7022b06fb76c0b00c23c970bc8df605b0ec63.zip |
Handle subtitles in player
Diffstat (limited to 'client/src')
8 files changed, 124 insertions, 29 deletions
diff --git a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts index 8d476393f..c77249a02 100644 --- a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts +++ b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts | |||
@@ -213,6 +213,7 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit { | |||
213 | servicesTwitterUsername: this.customConfig.services.twitter.username, | 213 | servicesTwitterUsername: this.customConfig.services.twitter.username, |
214 | servicesTwitterWhitelisted: this.customConfig.services.twitter.whitelisted, | 214 | servicesTwitterWhitelisted: this.customConfig.services.twitter.whitelisted, |
215 | cachePreviewsSize: this.customConfig.cache.previews.size, | 215 | cachePreviewsSize: this.customConfig.cache.previews.size, |
216 | cacheCaptionsSize: this.customConfig.cache.captions.size, | ||
216 | signupEnabled: this.customConfig.signup.enabled, | 217 | signupEnabled: this.customConfig.signup.enabled, |
217 | signupLimit: this.customConfig.signup.limit, | 218 | signupLimit: this.customConfig.signup.limit, |
218 | adminEmail: this.customConfig.admin.email, | 219 | adminEmail: this.customConfig.admin.email, |
diff --git a/client/src/app/videos/+video-watch/video-watch.component.ts b/client/src/app/videos/+video-watch/video-watch.component.ts index 8adf97d48..601c6a38d 100644 --- a/client/src/app/videos/+video-watch/video-watch.component.ts +++ b/client/src/app/videos/+video-watch/video-watch.component.ts | |||
@@ -6,11 +6,11 @@ import { peertubeLocalStorage } from '@app/shared/misc/peertube-local-storage' | |||
6 | import { VideoSupportComponent } from '@app/videos/+video-watch/modal/video-support.component' | 6 | import { VideoSupportComponent } from '@app/videos/+video-watch/modal/video-support.component' |
7 | import { MetaService } from '@ngx-meta/core' | 7 | import { MetaService } from '@ngx-meta/core' |
8 | import { NotificationsService } from 'angular2-notifications' | 8 | import { NotificationsService } from 'angular2-notifications' |
9 | import { Subscription } from 'rxjs' | 9 | import { forkJoin, Subscription } from 'rxjs' |
10 | import * as videojs from 'video.js' | 10 | import * as videojs from 'video.js' |
11 | import 'videojs-hotkeys' | 11 | import 'videojs-hotkeys' |
12 | import * as WebTorrent from 'webtorrent' | 12 | import * as WebTorrent from 'webtorrent' |
13 | import { UserVideoRateType, VideoPrivacy, VideoRateType, VideoState } from '../../../../../shared' | 13 | import { ResultList, UserVideoRateType, VideoPrivacy, VideoRateType, VideoState } from '../../../../../shared' |
14 | import '../../../assets/player/peertube-videojs-plugin' | 14 | import '../../../assets/player/peertube-videojs-plugin' |
15 | import { AuthService, ConfirmService } from '../../core' | 15 | import { AuthService, ConfirmService } from '../../core' |
16 | import { RestExtractor, VideoBlacklistService } from '../../shared' | 16 | import { RestExtractor, VideoBlacklistService } from '../../shared' |
@@ -26,6 +26,9 @@ import { ServerService } from '@app/core' | |||
26 | import { I18n } from '@ngx-translate/i18n-polyfill' | 26 | import { I18n } from '@ngx-translate/i18n-polyfill' |
27 | import { environment } from '../../../environments/environment' | 27 | import { environment } from '../../../environments/environment' |
28 | import { getDevLocale, isOnDevLocale } from '@app/shared/i18n/i18n-utils' | 28 | import { getDevLocale, isOnDevLocale } from '@app/shared/i18n/i18n-utils' |
29 | import { VideoCaptionService } from '@app/shared/video-caption' | ||
30 | import { VideoCaption } from '../../../../../shared/models/videos/video-caption.model' | ||
31 | import { VideoJSCaption } from '../../../assets/player/peertube-videojs-typings' | ||
29 | 32 | ||
30 | @Component({ | 33 | @Component({ |
31 | selector: 'my-video-watch', | 34 | selector: 'my-video-watch', |
@@ -74,6 +77,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
74 | private markdownService: MarkdownService, | 77 | private markdownService: MarkdownService, |
75 | private zone: NgZone, | 78 | private zone: NgZone, |
76 | private redirectService: RedirectService, | 79 | private redirectService: RedirectService, |
80 | private videoCaptionService: VideoCaptionService, | ||
77 | private i18n: I18n, | 81 | private i18n: I18n, |
78 | @Inject(LOCALE_ID) private localeId: string | 82 | @Inject(LOCALE_ID) private localeId: string |
79 | ) {} | 83 | ) {} |
@@ -109,14 +113,18 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
109 | if (this.player) this.player.pause() | 113 | if (this.player) this.player.pause() |
110 | 114 | ||
111 | // Video did change | 115 | // Video did change |
112 | this.videoService | 116 | forkJoin( |
113 | .getVideo(uuid) | 117 | this.videoService.getVideo(uuid), |
114 | .pipe(catchError(err => this.restExtractor.redirectTo404IfNotFound(err, [ 400, 404 ]))) | 118 | this.videoCaptionService.listCaptions(uuid) |
115 | .subscribe(video => { | 119 | ) |
116 | const startTime = this.route.snapshot.queryParams.start | 120 | .pipe( |
117 | this.onVideoFetched(video, startTime) | 121 | catchError(err => this.restExtractor.redirectTo404IfNotFound(err, [ 400, 404 ])) |
118 | .catch(err => this.handleError(err)) | 122 | ) |
119 | }) | 123 | .subscribe(([ video, captionsResult ]) => { |
124 | const startTime = this.route.snapshot.queryParams.start | ||
125 | this.onVideoFetched(video, captionsResult.data, startTime) | ||
126 | .catch(err => this.handleError(err)) | ||
127 | }) | ||
120 | }) | 128 | }) |
121 | } | 129 | } |
122 | 130 | ||
@@ -331,7 +339,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
331 | ) | 339 | ) |
332 | } | 340 | } |
333 | 341 | ||
334 | private async onVideoFetched (video: VideoDetails, startTime = 0) { | 342 | private async onVideoFetched (video: VideoDetails, videoCaptions: VideoCaption[], startTime = 0) { |
335 | this.video = video | 343 | this.video = video |
336 | 344 | ||
337 | // Re init attributes | 345 | // Re init attributes |
@@ -358,10 +366,17 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
358 | this.playerElement.setAttribute('playsinline', 'true') | 366 | this.playerElement.setAttribute('playsinline', 'true') |
359 | playerElementWrapper.appendChild(this.playerElement) | 367 | playerElementWrapper.appendChild(this.playerElement) |
360 | 368 | ||
369 | const playerCaptions = videoCaptions.map(c => ({ | ||
370 | label: c.language.label, | ||
371 | language: c.language.id, | ||
372 | src: environment.apiUrl + c.captionPath | ||
373 | })) | ||
374 | |||
361 | const videojsOptions = getVideojsOptions({ | 375 | const videojsOptions = getVideojsOptions({ |
362 | autoplay: this.isAutoplay(), | 376 | autoplay: this.isAutoplay(), |
363 | inactivityTimeout: 2500, | 377 | inactivityTimeout: 2500, |
364 | videoFiles: this.video.files, | 378 | videoFiles: this.video.files, |
379 | videoCaptions: playerCaptions, | ||
365 | playerElement: this.playerElement, | 380 | playerElement: this.playerElement, |
366 | videoViewUrl: this.video.privacy.id !== VideoPrivacy.PRIVATE ? this.videoService.getVideoViewUrl(this.video.uuid) : null, | 381 | videoViewUrl: this.video.privacy.id !== VideoPrivacy.PRIVATE ? this.videoService.getVideoViewUrl(this.video.uuid) : null, |
367 | videoDuration: this.video.duration, | 382 | videoDuration: this.video.duration, |
diff --git a/client/src/assets/player/peertube-player.ts b/client/src/assets/player/peertube-player.ts index baae740fe..bf02ce91c 100644 --- a/client/src/assets/player/peertube-player.ts +++ b/client/src/assets/player/peertube-player.ts | |||
@@ -11,12 +11,16 @@ import './webtorrent-info-button' | |||
11 | import './peertube-videojs-plugin' | 11 | import './peertube-videojs-plugin' |
12 | import './peertube-load-progress-bar' | 12 | import './peertube-load-progress-bar' |
13 | import './theater-button' | 13 | import './theater-button' |
14 | import { videojsUntyped } from './peertube-videojs-typings' | 14 | import { VideoJSCaption, videojsUntyped } from './peertube-videojs-typings' |
15 | import { buildVideoEmbed, buildVideoLink, copyToClipboard } from './utils' | 15 | import { buildVideoEmbed, buildVideoLink, copyToClipboard } from './utils' |
16 | import { getCompleteLocale, getShortLocale, is18nLocale, isDefaultLocale } from '../../../../shared/models/i18n/i18n' | 16 | import { getCompleteLocale, getShortLocale, is18nLocale, isDefaultLocale } from '../../../../shared/models/i18n/i18n' |
17 | 17 | ||
18 | // Change 'Playback Rate' to 'Speed' (smaller for our settings menu) | 18 | // Change 'Playback Rate' to 'Speed' (smaller for our settings menu) |
19 | videojsUntyped.getComponent('PlaybackRateMenuButton').prototype.controlText_ = 'Speed' | 19 | videojsUntyped.getComponent('PlaybackRateMenuButton').prototype.controlText_ = 'Speed' |
20 | // Change Captions to Subtitles/CC | ||
21 | videojsUntyped.getComponent('CaptionsButton').prototype.controlText_ = 'Subtitles/CC' | ||
22 | // We just want to display 'Off' instead of 'captions off', keep a space so the variable == true (hacky I know) | ||
23 | videojsUntyped.getComponent('CaptionsButton').prototype.label_ = ' ' | ||
20 | 24 | ||
21 | function getVideojsOptions (options: { | 25 | function getVideojsOptions (options: { |
22 | autoplay: boolean, | 26 | autoplay: boolean, |
@@ -30,11 +34,14 @@ function getVideojsOptions (options: { | |||
30 | poster: string, | 34 | poster: string, |
31 | startTime: number | 35 | startTime: number |
32 | theaterMode: boolean, | 36 | theaterMode: boolean, |
37 | videoCaptions: VideoJSCaption[], | ||
33 | controls?: boolean, | 38 | controls?: boolean, |
34 | muted?: boolean, | 39 | muted?: boolean, |
35 | loop?: boolean | 40 | loop?: boolean |
36 | }) { | 41 | }) { |
37 | const videojsOptions = { | 42 | const videojsOptions = { |
43 | // We don't use text track settings for now | ||
44 | textTrackSettings: false, | ||
38 | controls: options.controls !== undefined ? options.controls : true, | 45 | controls: options.controls !== undefined ? options.controls : true, |
39 | muted: options.controls !== undefined ? options.muted : false, | 46 | muted: options.controls !== undefined ? options.muted : false, |
40 | loop: options.loop !== undefined ? options.loop : false, | 47 | loop: options.loop !== undefined ? options.loop : false, |
@@ -45,6 +52,7 @@ function getVideojsOptions (options: { | |||
45 | plugins: { | 52 | plugins: { |
46 | peertube: { | 53 | peertube: { |
47 | autoplay: options.autoplay, // Use peertube plugin autoplay because we get the file by webtorrent | 54 | autoplay: options.autoplay, // Use peertube plugin autoplay because we get the file by webtorrent |
55 | videoCaptions: options.videoCaptions, | ||
48 | videoFiles: options.videoFiles, | 56 | videoFiles: options.videoFiles, |
49 | playerElement: options.playerElement, | 57 | playerElement: options.playerElement, |
50 | videoViewUrl: options.videoViewUrl, | 58 | videoViewUrl: options.videoViewUrl, |
@@ -71,8 +79,16 @@ function getVideojsOptions (options: { | |||
71 | 79 | ||
72 | function getControlBarChildren (options: { | 80 | function getControlBarChildren (options: { |
73 | peertubeLink: boolean | 81 | peertubeLink: boolean |
74 | theaterMode: boolean | 82 | theaterMode: boolean, |
83 | videoCaptions: VideoJSCaption[] | ||
75 | }) { | 84 | }) { |
85 | const settingEntries = [] | ||
86 | |||
87 | // Keep an order | ||
88 | settingEntries.push('playbackRateMenuButton') | ||
89 | if (options.videoCaptions.length !== 0) settingEntries.push('captionsButton') | ||
90 | settingEntries.push('resolutionMenuButton') | ||
91 | |||
76 | const children = { | 92 | const children = { |
77 | 'playToggle': {}, | 93 | 'playToggle': {}, |
78 | 'currentTimeDisplay': {}, | 94 | 'currentTimeDisplay': {}, |
@@ -102,10 +118,7 @@ function getControlBarChildren (options: { | |||
102 | setup: { | 118 | setup: { |
103 | maxHeightOffset: 40 | 119 | maxHeightOffset: 40 |
104 | }, | 120 | }, |
105 | entries: [ | 121 | entries: settingEntries |
106 | 'resolutionMenuButton', | ||
107 | 'playbackRateMenuButton' | ||
108 | ] | ||
109 | } | 122 | } |
110 | } | 123 | } |
111 | 124 | ||
diff --git a/client/src/assets/player/peertube-videojs-plugin.ts b/client/src/assets/player/peertube-videojs-plugin.ts index 57c894ee6..3f6fc4cc6 100644 --- a/client/src/assets/player/peertube-videojs-plugin.ts +++ b/client/src/assets/player/peertube-videojs-plugin.ts | |||
@@ -3,7 +3,7 @@ import * as WebTorrent from 'webtorrent' | |||
3 | import { VideoFile } from '../../../../shared/models/videos/video.model' | 3 | import { VideoFile } from '../../../../shared/models/videos/video.model' |
4 | import { renderVideo } from './video-renderer' | 4 | import { renderVideo } from './video-renderer' |
5 | import './settings-menu-button' | 5 | import './settings-menu-button' |
6 | import { PeertubePluginOptions, VideoJSComponentInterface, videojsUntyped } from './peertube-videojs-typings' | 6 | import { PeertubePluginOptions, VideoJSCaption, VideoJSComponentInterface, videojsUntyped } from './peertube-videojs-typings' |
7 | import { isMobile, videoFileMaxByResolution, videoFileMinByResolution } from './utils' | 7 | import { isMobile, videoFileMaxByResolution, videoFileMinByResolution } from './utils' |
8 | import * as CacheChunkStore from 'cache-chunk-store' | 8 | import * as CacheChunkStore from 'cache-chunk-store' |
9 | import { PeertubeChunkStore } from './peertube-chunk-store' | 9 | import { PeertubeChunkStore } from './peertube-chunk-store' |
@@ -54,6 +54,7 @@ class PeerTubePlugin extends Plugin { | |||
54 | private player: any | 54 | private player: any |
55 | private currentVideoFile: VideoFile | 55 | private currentVideoFile: VideoFile |
56 | private torrent: WebTorrent.Torrent | 56 | private torrent: WebTorrent.Torrent |
57 | private videoCaptions: VideoJSCaption[] | ||
57 | private renderer | 58 | private renderer |
58 | private fakeRenderer | 59 | private fakeRenderer |
59 | private autoResolution = true | 60 | private autoResolution = true |
@@ -79,6 +80,7 @@ class PeerTubePlugin extends Plugin { | |||
79 | this.videoFiles = options.videoFiles | 80 | this.videoFiles = options.videoFiles |
80 | this.videoViewUrl = options.videoViewUrl | 81 | this.videoViewUrl = options.videoViewUrl |
81 | this.videoDuration = options.videoDuration | 82 | this.videoDuration = options.videoDuration |
83 | this.videoCaptions = options.videoCaptions | ||
82 | 84 | ||
83 | this.savePlayerSrcFunction = this.player.src | 85 | this.savePlayerSrcFunction = this.player.src |
84 | // Hack to "simulate" src link in video.js >= 6 | 86 | // Hack to "simulate" src link in video.js >= 6 |
@@ -421,6 +423,8 @@ class PeerTubePlugin extends Plugin { | |||
421 | 423 | ||
422 | this.initSmoothProgressBar() | 424 | this.initSmoothProgressBar() |
423 | 425 | ||
426 | this.initCaptions() | ||
427 | |||
424 | this.alterInactivity() | 428 | this.alterInactivity() |
425 | 429 | ||
426 | if (this.autoplay === true) { | 430 | if (this.autoplay === true) { |
@@ -581,7 +585,7 @@ class PeerTubePlugin extends Plugin { | |||
581 | this.player.options_.inactivityTimeout = 0 | 585 | this.player.options_.inactivityTimeout = 0 |
582 | } | 586 | } |
583 | const enableInactivity = () => { | 587 | const enableInactivity = () => { |
584 | this.player.options_.inactivityTimeout = saveInactivityTimeout | 588 | // this.player.options_.inactivityTimeout = saveInactivityTimeout |
585 | } | 589 | } |
586 | 590 | ||
587 | const settingsDialog = this.player.children_.find(c => c.name_ === 'SettingsDialog') | 591 | const settingsDialog = this.player.children_.find(c => c.name_ === 'SettingsDialog') |
@@ -611,6 +615,18 @@ class PeerTubePlugin extends Plugin { | |||
611 | } | 615 | } |
612 | } | 616 | } |
613 | 617 | ||
618 | private initCaptions () { | ||
619 | for (const caption of this.videoCaptions) { | ||
620 | this.player.addRemoteTextTrack({ | ||
621 | kind: 'captions', | ||
622 | label: caption.label, | ||
623 | language: caption.language, | ||
624 | id: caption.language, | ||
625 | src: caption.src | ||
626 | }, false) | ||
627 | } | ||
628 | } | ||
629 | |||
614 | // Thanks: https://github.com/videojs/video.js/issues/4460#issuecomment-312861657 | 630 | // Thanks: https://github.com/videojs/video.js/issues/4460#issuecomment-312861657 |
615 | private initSmoothProgressBar () { | 631 | private initSmoothProgressBar () { |
616 | const SeekBar = videojsUntyped.getComponent('SeekBar') | 632 | const SeekBar = videojsUntyped.getComponent('SeekBar') |
diff --git a/client/src/assets/player/peertube-videojs-typings.ts b/client/src/assets/player/peertube-videojs-typings.ts index 50d6039ea..9c0299237 100644 --- a/client/src/assets/player/peertube-videojs-typings.ts +++ b/client/src/assets/player/peertube-videojs-typings.ts | |||
@@ -16,13 +16,20 @@ interface VideoJSComponentInterface { | |||
16 | registerComponent (name: string, obj: any) | 16 | registerComponent (name: string, obj: any) |
17 | } | 17 | } |
18 | 18 | ||
19 | type VideoJSCaption = { | ||
20 | label: string | ||
21 | language: string | ||
22 | src: string | ||
23 | } | ||
24 | |||
19 | type PeertubePluginOptions = { | 25 | type PeertubePluginOptions = { |
20 | videoFiles: VideoFile[] | 26 | videoFiles: VideoFile[] |
21 | playerElement: HTMLVideoElement | 27 | playerElement: HTMLVideoElement |
22 | videoViewUrl: string | 28 | videoViewUrl: string |
23 | videoDuration: number | 29 | videoDuration: number |
24 | startTime: number | 30 | startTime: number |
25 | autoplay: boolean | 31 | autoplay: boolean, |
32 | videoCaptions: VideoJSCaption[] | ||
26 | } | 33 | } |
27 | 34 | ||
28 | // videojs typings don't have some method we need | 35 | // videojs typings don't have some method we need |
@@ -31,5 +38,6 @@ const videojsUntyped = videojs as any | |||
31 | export { | 38 | export { |
32 | VideoJSComponentInterface, | 39 | VideoJSComponentInterface, |
33 | PeertubePluginOptions, | 40 | PeertubePluginOptions, |
34 | videojsUntyped | 41 | videojsUntyped, |
42 | VideoJSCaption | ||
35 | } | 43 | } |
diff --git a/client/src/assets/player/settings-menu-item.ts b/client/src/assets/player/settings-menu-item.ts index 88985e1ae..6e2224e20 100644 --- a/client/src/assets/player/settings-menu-item.ts +++ b/client/src/assets/player/settings-menu-item.ts | |||
@@ -32,6 +32,8 @@ class SettingsMenuItem extends MenuItem { | |||
32 | throw new Error(`Component ${subMenuName} does not exist`) | 32 | throw new Error(`Component ${subMenuName} does not exist`) |
33 | } | 33 | } |
34 | this.subMenu = new SubMenuComponent(this.player(), options, menuButton, this) | 34 | this.subMenu = new SubMenuComponent(this.player(), options, menuButton, this) |
35 | const subMenuClass = this.subMenu.buildCSSClass().split(' ')[0] | ||
36 | this.settingsSubMenuEl_.className += ' ' + subMenuClass | ||
35 | 37 | ||
36 | this.eventHandlers() | 38 | this.eventHandlers() |
37 | 39 | ||
diff --git a/client/src/sass/player/settings-menu.scss b/client/src/sass/player/settings-menu.scss index 0c064c182..d065e72fb 100644 --- a/client/src/sass/player/settings-menu.scss +++ b/client/src/sass/player/settings-menu.scss | |||
@@ -52,6 +52,7 @@ $setting-transition-easing: ease-out; | |||
52 | .vjs-settings-sub-menu-title { | 52 | .vjs-settings-sub-menu-title { |
53 | display: table-cell; | 53 | display: table-cell; |
54 | padding: 0 5px; | 54 | padding: 0 5px; |
55 | text-transform: capitalize; | ||
55 | } | 56 | } |
56 | 57 | ||
57 | .vjs-settings-sub-menu-title { | 58 | .vjs-settings-sub-menu-title { |
@@ -141,15 +142,15 @@ $setting-transition-easing: ease-out; | |||
141 | .vjs-menu-item { | 142 | .vjs-menu-item { |
142 | outline: 0; | 143 | outline: 0; |
143 | font-weight: $font-semibold; | 144 | font-weight: $font-semibold; |
144 | |||
145 | padding: 5px 8px; | ||
146 | text-align: right; | 145 | text-align: right; |
146 | padding: 5px 8px; | ||
147 | 147 | ||
148 | &.vjs-back-button { | 148 | &.vjs-back-button { |
149 | background-color: inherit; | 149 | background-color: inherit; |
150 | padding: 8px 8px 13px 8px; | 150 | padding: 8px 8px 13px 12px; |
151 | margin-bottom: 5px; | 151 | margin-bottom: 5px; |
152 | border-bottom: 1px solid grey; | 152 | border-bottom: 1px solid grey; |
153 | text-align: left; | ||
153 | 154 | ||
154 | &::before { | 155 | &::before { |
155 | @include chevron-left(9px, 2px); | 156 | @include chevron-left(9px, 2px); |
@@ -174,6 +175,25 @@ $setting-transition-easing: ease-out; | |||
174 | } | 175 | } |
175 | } | 176 | } |
176 | } | 177 | } |
178 | |||
179 | // Special captions case | ||
180 | // Bigger caption button | ||
181 | &.vjs-captions-button { | ||
182 | width: 200px; | ||
183 | |||
184 | .vjs-menu-item { | ||
185 | text-align: left; | ||
186 | |||
187 | .vjs-menu-item-text { | ||
188 | margin-left: 25px; | ||
189 | text-transform: capitalize; | ||
190 | } | ||
191 | } | ||
192 | } | ||
193 | |||
194 | .vjs-menu { | ||
195 | width: inherit; | ||
196 | } | ||
177 | } | 197 | } |
178 | } | 198 | } |
179 | } | 199 | } |
diff --git a/client/src/standalone/videos/embed.ts b/client/src/standalone/videos/embed.ts index a4196600a..1275998b8 100644 --- a/client/src/standalone/videos/embed.ts +++ b/client/src/standalone/videos/embed.ts | |||
@@ -20,9 +20,11 @@ import 'whatwg-fetch' | |||
20 | import * as vjs from 'video.js' | 20 | import * as vjs from 'video.js' |
21 | import * as Channel from 'jschannel' | 21 | import * as Channel from 'jschannel' |
22 | 22 | ||
23 | import { VideoDetails } from '../../../../shared' | 23 | import { ResultList, VideoDetails } from '../../../../shared' |
24 | import { addContextMenu, getVideojsOptions, loadLocale } from '../../assets/player/peertube-player' | 24 | import { addContextMenu, getVideojsOptions, loadLocale } from '../../assets/player/peertube-player' |
25 | import { PeerTubeResolution } from '../player/definitions' | 25 | import { PeerTubeResolution } from '../player/definitions' |
26 | import { VideoJSCaption } from '../../assets/player/peertube-videojs-typings' | ||
27 | import { VideoCaption } from '../../../../shared/models/videos/video-caption.model' | ||
26 | 28 | ||
27 | /** | 29 | /** |
28 | * Embed API exposes control of the embed player to the outside world via | 30 | * Embed API exposes control of the embed player to the outside world via |
@@ -178,6 +180,10 @@ class PeerTubeEmbed { | |||
178 | return fetch(this.getVideoUrl(videoId)) | 180 | return fetch(this.getVideoUrl(videoId)) |
179 | } | 181 | } |
180 | 182 | ||
183 | loadVideoCaptions (videoId: string): Promise<Response> { | ||
184 | return fetch(this.getVideoUrl(videoId) + '/captions') | ||
185 | } | ||
186 | |||
181 | removeElement (element: HTMLElement) { | 187 | removeElement (element: HTMLElement) { |
182 | element.parentElement.removeChild(element) | 188 | element.parentElement.removeChild(element) |
183 | } | 189 | } |
@@ -254,15 +260,27 @@ class PeerTubeEmbed { | |||
254 | const videoId = lastPart.indexOf('?') === -1 ? lastPart : lastPart.split('?')[ 0 ] | 260 | const videoId = lastPart.indexOf('?') === -1 ? lastPart : lastPart.split('?')[ 0 ] |
255 | 261 | ||
256 | await loadLocale(window.location.origin, vjs, navigator.language) | 262 | await loadLocale(window.location.origin, vjs, navigator.language) |
257 | let response = await this.loadVideoInfo(videoId) | 263 | const [ videoResponse, captionsResponse ] = await Promise.all([ |
264 | this.loadVideoInfo(videoId), | ||
265 | this.loadVideoCaptions(videoId) | ||
266 | ]) | ||
258 | 267 | ||
259 | if (!response.ok) { | 268 | if (!videoResponse.ok) { |
260 | if (response.status === 404) return this.videoNotFound(this.videoElement) | 269 | if (videoResponse.status === 404) return this.videoNotFound(this.videoElement) |
261 | 270 | ||
262 | return this.videoFetchError(this.videoElement) | 271 | return this.videoFetchError(this.videoElement) |
263 | } | 272 | } |
264 | 273 | ||
265 | const videoInfo: VideoDetails = await response.json() | 274 | const videoInfo: VideoDetails = await videoResponse.json() |
275 | let videoCaptions: VideoJSCaption[] = [] | ||
276 | if (captionsResponse.ok) { | ||
277 | const { data } = (await captionsResponse.json()) as ResultList<VideoCaption> | ||
278 | videoCaptions = data.map(c => ({ | ||
279 | label: c.language.label, | ||
280 | language: c.language.id, | ||
281 | src: window.location.origin + c.captionPath | ||
282 | })) | ||
283 | } | ||
266 | 284 | ||
267 | this.loadParams() | 285 | this.loadParams() |
268 | 286 | ||
@@ -273,6 +291,7 @@ class PeerTubeEmbed { | |||
273 | loop: this.loop, | 291 | loop: this.loop, |
274 | startTime: this.startTime, | 292 | startTime: this.startTime, |
275 | 293 | ||
294 | videoCaptions, | ||
276 | inactivityTimeout: 1500, | 295 | inactivityTimeout: 1500, |
277 | videoViewUrl: this.getVideoUrl(videoId) + '/views', | 296 | videoViewUrl: this.getVideoUrl(videoId) + '/views', |
278 | playerElement: this.videoElement, | 297 | playerElement: this.videoElement, |
@@ -297,6 +316,7 @@ class PeerTubeEmbed { | |||
297 | } | 316 | } |
298 | 317 | ||
299 | addContextMenu(this.player, window.location.origin + videoInfo.embedPath) | 318 | addContextMenu(this.player, window.location.origin + videoInfo.embedPath) |
319 | |||
300 | this.initializeApi() | 320 | this.initializeApi() |
301 | }) | 321 | }) |
302 | } | 322 | } |