diff options
-rw-r--r-- | client/src/assets/player/shared/webtorrent/webtorrent-plugin.ts | 5 | ||||
-rw-r--r-- | client/src/sass/player/peertube-skin.scss | 9 | ||||
-rw-r--r-- | client/src/standalone/videos/embed.scss | 11 | ||||
-rw-r--r-- | client/src/standalone/videos/embed.ts | 17 | ||||
-rw-r--r-- | client/src/standalone/videos/shared/index.ts | 1 | ||||
-rw-r--r-- | client/src/standalone/videos/shared/live-manager.ts | 69 | ||||
-rw-r--r-- | client/src/standalone/videos/shared/player-html.ts | 15 | ||||
-rw-r--r-- | server/lib/client-html.ts | 6 |
8 files changed, 130 insertions, 3 deletions
diff --git a/client/src/assets/player/shared/webtorrent/webtorrent-plugin.ts b/client/src/assets/player/shared/webtorrent/webtorrent-plugin.ts index b48203148..83b483d87 100644 --- a/client/src/assets/player/shared/webtorrent/webtorrent-plugin.ts +++ b/client/src/assets/player/shared/webtorrent/webtorrent-plugin.ts | |||
@@ -430,6 +430,11 @@ class WebTorrentPlugin extends Plugin { | |||
430 | private initializePlayer () { | 430 | private initializePlayer () { |
431 | this.buildQualities() | 431 | this.buildQualities() |
432 | 432 | ||
433 | if (this.videoFiles.length === 0) { | ||
434 | this.player.addClass('disabled') | ||
435 | return | ||
436 | } | ||
437 | |||
433 | if (this.autoplay) { | 438 | if (this.autoplay) { |
434 | this.player.posterImage.hide() | 439 | this.player.posterImage.hide() |
435 | 440 | ||
diff --git a/client/src/sass/player/peertube-skin.scss b/client/src/sass/player/peertube-skin.scss index c420e825e..43c144624 100644 --- a/client/src/sass/player/peertube-skin.scss +++ b/client/src/sass/player/peertube-skin.scss | |||
@@ -20,6 +20,15 @@ body { | |||
20 | font-size: $font-size; | 20 | font-size: $font-size; |
21 | color: pvar(--embedForegroundColor); | 21 | color: pvar(--embedForegroundColor); |
22 | 22 | ||
23 | &.disabled { | ||
24 | cursor: default; | ||
25 | pointer-events: none; | ||
26 | |||
27 | .vjs-big-play-button { | ||
28 | display: none !important; | ||
29 | } | ||
30 | } | ||
31 | |||
23 | .vjs-audio-button { | 32 | .vjs-audio-button { |
24 | display: none; | 33 | display: none; |
25 | } | 34 | } |
diff --git a/client/src/standalone/videos/embed.scss b/client/src/standalone/videos/embed.scss index 91ab822c8..8c20bae79 100644 --- a/client/src/standalone/videos/embed.scss +++ b/client/src/standalone/videos/embed.scss | |||
@@ -92,6 +92,17 @@ body { | |||
92 | width: 100%; | 92 | width: 100%; |
93 | height: 100%; | 93 | height: 100%; |
94 | background-position: 50% 50%; | 94 | background-position: 50% 50%; |
95 | background-repeat: no-repeat; | ||
96 | } | ||
97 | |||
98 | .player-information { | ||
99 | width: 100%; | ||
100 | color: #fff; | ||
101 | background: rgba(0, 0, 0, 0.6); | ||
102 | padding: 20px 0; | ||
103 | position: absolute; | ||
104 | bottom: 0; | ||
105 | text-align: center; | ||
95 | } | 106 | } |
96 | 107 | ||
97 | @media screen and (max-width: 300px) { | 108 | @media screen and (max-width: 300px) { |
diff --git a/client/src/standalone/videos/embed.ts b/client/src/standalone/videos/embed.ts index c5d017d4a..0a2b0ccbd 100644 --- a/client/src/standalone/videos/embed.ts +++ b/client/src/standalone/videos/embed.ts | |||
@@ -4,12 +4,12 @@ import '../../assets/player/shared/dock/peertube-dock-plugin' | |||
4 | import videojs from 'video.js' | 4 | import videojs from 'video.js' |
5 | import { peertubeTranslate } from '../../../../shared/core-utils/i18n' | 5 | import { peertubeTranslate } from '../../../../shared/core-utils/i18n' |
6 | import { HTMLServerConfig, LiveVideo, ResultList, VideoDetails, VideoPlaylist, VideoPlaylistElement } from '../../../../shared/models' | 6 | import { HTMLServerConfig, LiveVideo, ResultList, VideoDetails, VideoPlaylist, VideoPlaylistElement } from '../../../../shared/models' |
7 | import { PeertubePlayerManager } from '../../assets/player' | ||
7 | import { TranslationsManager } from '../../assets/player/translations-manager' | 8 | import { TranslationsManager } from '../../assets/player/translations-manager' |
8 | import { getParamString } from '../../root-helpers' | 9 | import { getParamString } from '../../root-helpers' |
9 | import { PeerTubeEmbedApi } from './embed-api' | 10 | import { PeerTubeEmbedApi } from './embed-api' |
10 | import { AuthHTTP, PeerTubePlugin, PlayerManagerOptions, PlaylistFetcher, PlaylistTracker, VideoFetcher } from './shared' | 11 | import { AuthHTTP, LiveManager, PeerTubePlugin, PlayerManagerOptions, PlaylistFetcher, PlaylistTracker, VideoFetcher } from './shared' |
11 | import { PlayerHTML } from './shared/player-html' | 12 | import { PlayerHTML } from './shared/player-html' |
12 | import { PeertubePlayerManager } from '../../assets/player' | ||
13 | 13 | ||
14 | export class PeerTubeEmbed { | 14 | export class PeerTubeEmbed { |
15 | player: videojs.Player | 15 | player: videojs.Player |
@@ -26,6 +26,7 @@ export class PeerTubeEmbed { | |||
26 | private readonly peertubePlugin: PeerTubePlugin | 26 | private readonly peertubePlugin: PeerTubePlugin |
27 | private readonly playerHTML: PlayerHTML | 27 | private readonly playerHTML: PlayerHTML |
28 | private readonly playerManagerOptions: PlayerManagerOptions | 28 | private readonly playerManagerOptions: PlayerManagerOptions |
29 | private readonly liveManager: LiveManager | ||
29 | 30 | ||
30 | private playlistTracker: PlaylistTracker | 31 | private playlistTracker: PlaylistTracker |
31 | 32 | ||
@@ -37,6 +38,7 @@ export class PeerTubeEmbed { | |||
37 | this.peertubePlugin = new PeerTubePlugin(this.http) | 38 | this.peertubePlugin = new PeerTubePlugin(this.http) |
38 | this.playerHTML = new PlayerHTML(videoWrapperId) | 39 | this.playerHTML = new PlayerHTML(videoWrapperId) |
39 | this.playerManagerOptions = new PlayerManagerOptions(this.playerHTML, this.videoFetcher, this.peertubePlugin) | 40 | this.playerManagerOptions = new PlayerManagerOptions(this.playerHTML, this.videoFetcher, this.peertubePlugin) |
41 | this.liveManager = new LiveManager(this.playerHTML) | ||
40 | 42 | ||
41 | try { | 43 | try { |
42 | this.config = JSON.parse(window['PeerTubeServerConfig']) | 44 | this.config = JSON.parse(window['PeerTubeServerConfig']) |
@@ -235,6 +237,17 @@ export class PeerTubeEmbed { | |||
235 | } | 237 | } |
236 | 238 | ||
237 | this.peertubePlugin.getPluginsManager().runHook('action:embed.player.loaded', undefined, { player: this.player, videojs, video }) | 239 | this.peertubePlugin.getPluginsManager().runHook('action:embed.player.loaded', undefined, { player: this.player, videojs, video }) |
240 | |||
241 | if (video.isLive) { | ||
242 | this.liveManager.displayInfoAndListenForChanges({ | ||
243 | video, | ||
244 | translations, | ||
245 | onPublishedVideo: () => { | ||
246 | this.liveManager.stopListeningForChanges(video) | ||
247 | this.loadVideoAndBuildPlayer(video.uuid) | ||
248 | } | ||
249 | }) | ||
250 | } | ||
238 | } | 251 | } |
239 | 252 | ||
240 | private resetPlayerElement () { | 253 | private resetPlayerElement () { |
diff --git a/client/src/standalone/videos/shared/index.ts b/client/src/standalone/videos/shared/index.ts index 4b4e05b7c..928b8e270 100644 --- a/client/src/standalone/videos/shared/index.ts +++ b/client/src/standalone/videos/shared/index.ts | |||
@@ -1,5 +1,6 @@ | |||
1 | export * from './auth-http' | 1 | export * from './auth-http' |
2 | export * from './peertube-plugin' | 2 | export * from './peertube-plugin' |
3 | export * from './live-manager' | ||
3 | export * from './player-html' | 4 | export * from './player-html' |
4 | export * from './player-manager-options' | 5 | export * from './player-manager-options' |
5 | export * from './playlist-fetcher' | 6 | export * from './playlist-fetcher' |
diff --git a/client/src/standalone/videos/shared/live-manager.ts b/client/src/standalone/videos/shared/live-manager.ts new file mode 100644 index 000000000..422d39793 --- /dev/null +++ b/client/src/standalone/videos/shared/live-manager.ts | |||
@@ -0,0 +1,69 @@ | |||
1 | import { Socket } from 'socket.io-client' | ||
2 | import { LiveVideoEventPayload, VideoDetails, VideoState } from '../../../../../shared/models' | ||
3 | import { PlayerHTML } from './player-html' | ||
4 | import { Translations } from './translations' | ||
5 | |||
6 | export class LiveManager { | ||
7 | private liveSocket: Socket | ||
8 | |||
9 | constructor ( | ||
10 | private readonly playerHTML: PlayerHTML | ||
11 | ) { | ||
12 | |||
13 | } | ||
14 | |||
15 | async displayInfoAndListenForChanges (options: { | ||
16 | video: VideoDetails | ||
17 | translations: Translations | ||
18 | onPublishedVideo: () => any | ||
19 | }) { | ||
20 | const { video, onPublishedVideo } = options | ||
21 | |||
22 | this.displayAppropriateInfo(options) | ||
23 | |||
24 | if (!this.liveSocket) { | ||
25 | const io = (await import('socket.io-client')).io | ||
26 | this.liveSocket = io(window.location.origin + '/live-videos') | ||
27 | } | ||
28 | |||
29 | this.liveSocket.on('state-change', (payload: LiveVideoEventPayload) => { | ||
30 | if (payload.state === VideoState.PUBLISHED) { | ||
31 | this.playerHTML.removeInformation() | ||
32 | onPublishedVideo() | ||
33 | return | ||
34 | } | ||
35 | }) | ||
36 | |||
37 | this.liveSocket.emit('subscribe', { videoId: video.id }) | ||
38 | } | ||
39 | |||
40 | stopListeningForChanges (video: VideoDetails) { | ||
41 | this.liveSocket.emit('unsubscribe', { videoId: video.id }) | ||
42 | } | ||
43 | |||
44 | private displayAppropriateInfo (options: { | ||
45 | video: VideoDetails | ||
46 | translations: Translations | ||
47 | }) { | ||
48 | const { video, translations } = options | ||
49 | |||
50 | if (video.state.id === VideoState.WAITING_FOR_LIVE) { | ||
51 | this.displayWaitingForLiveInfo(translations) | ||
52 | return | ||
53 | } | ||
54 | |||
55 | if (video.state.id === VideoState.LIVE_ENDED) { | ||
56 | this.displayEndedLiveInfo(translations) | ||
57 | return | ||
58 | } | ||
59 | } | ||
60 | |||
61 | private displayWaitingForLiveInfo (translations: Translations) { | ||
62 | this.playerHTML.displayInformation('This live has not started yet.', translations) | ||
63 | } | ||
64 | |||
65 | private displayEndedLiveInfo (translations: Translations) { | ||
66 | this.playerHTML.displayInformation('This live has ended.', translations) | ||
67 | |||
68 | } | ||
69 | } | ||
diff --git a/client/src/standalone/videos/shared/player-html.ts b/client/src/standalone/videos/shared/player-html.ts index 110124417..eb6324ac7 100644 --- a/client/src/standalone/videos/shared/player-html.ts +++ b/client/src/standalone/videos/shared/player-html.ts | |||
@@ -6,6 +6,7 @@ export class PlayerHTML { | |||
6 | private readonly wrapperElement: HTMLElement | 6 | private readonly wrapperElement: HTMLElement |
7 | 7 | ||
8 | private playerElement: HTMLVideoElement | 8 | private playerElement: HTMLVideoElement |
9 | private informationElement: HTMLDivElement | ||
9 | 10 | ||
10 | constructor (private readonly videoWrapperId: string) { | 11 | constructor (private readonly videoWrapperId: string) { |
11 | this.wrapperElement = document.getElementById(this.videoWrapperId) | 12 | this.wrapperElement = document.getElementById(this.videoWrapperId) |
@@ -66,6 +67,20 @@ export class PlayerHTML { | |||
66 | placeholder.style.display = 'none' | 67 | placeholder.style.display = 'none' |
67 | } | 68 | } |
68 | 69 | ||
70 | displayInformation (text: string, translations: Translations) { | ||
71 | if (this.informationElement) this.removeInformation() | ||
72 | |||
73 | this.informationElement = document.createElement('div') | ||
74 | this.informationElement.className = 'player-information' | ||
75 | this.informationElement.innerText = peertubeTranslate(text, translations) | ||
76 | |||
77 | document.body.appendChild(this.informationElement) | ||
78 | } | ||
79 | |||
80 | removeInformation () { | ||
81 | this.removeElement(this.informationElement) | ||
82 | } | ||
83 | |||
69 | private getPlaceholderElement () { | 84 | private getPlaceholderElement () { |
70 | return document.getElementById('placeholder-preview') | 85 | return document.getElementById('placeholder-preview') |
71 | } | 86 | } |
diff --git a/server/lib/client-html.ts b/server/lib/client-html.ts index 337364ac9..1e8d03023 100644 --- a/server/lib/client-html.ts +++ b/server/lib/client-html.ts | |||
@@ -30,6 +30,7 @@ import { MAccountActor, MChannelActor } from '../types/models' | |||
30 | import { getActivityStreamDuration } from './activitypub/activity' | 30 | import { getActivityStreamDuration } from './activitypub/activity' |
31 | import { getBiggestActorImage } from './actor-image' | 31 | import { getBiggestActorImage } from './actor-image' |
32 | import { ServerConfigManager } from './server-config-manager' | 32 | import { ServerConfigManager } from './server-config-manager' |
33 | import { isTestInstance } from '@server/helpers/core-utils' | ||
33 | 34 | ||
34 | type Tags = { | 35 | type Tags = { |
35 | ogType: string | 36 | ogType: string |
@@ -232,7 +233,10 @@ class ClientHtml { | |||
232 | static async getEmbedHTML () { | 233 | static async getEmbedHTML () { |
233 | const path = ClientHtml.getEmbedPath() | 234 | const path = ClientHtml.getEmbedPath() |
234 | 235 | ||
235 | if (ClientHtml.htmlCache[path]) return ClientHtml.htmlCache[path] | 236 | // Disable HTML cache in dev mode because webpack can regenerate JS files |
237 | if (!isTestInstance() && ClientHtml.htmlCache[path]) { | ||
238 | return ClientHtml.htmlCache[path] | ||
239 | } | ||
236 | 240 | ||
237 | const buffer = await readFile(path) | 241 | const buffer = await readFile(path) |
238 | const serverConfig = await ServerConfigManager.Instance.getHTMLServerConfig() | 242 | const serverConfig = await ServerConfigManager.Instance.getHTMLServerConfig() |