aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--client/src/app/+videos/+video-watch/video-watch.component.ts50
-rw-r--r--client/src/app/core/notification/peertube-socket.service.ts7
-rw-r--r--server/lib/activitypub/videos.ts9
-rw-r--r--server/lib/job-queue/handlers/video-live-ending.ts2
-rw-r--r--server/lib/live-manager.ts2
-rw-r--r--server/lib/peertube-socket.ts13
-rw-r--r--server/tests/api/live/live.ts51
-rw-r--r--shared/models/videos/live/live-video-event-payload.model.ts3
-rw-r--r--shared/models/videos/live/live-video-event.type.ts2
9 files changed, 114 insertions, 25 deletions
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 33de901c0..7eb56eb48 100644
--- a/client/src/app/+videos/+video-watch/video-watch.component.ts
+++ b/client/src/app/+videos/+video-watch/video-watch.component.ts
@@ -25,6 +25,7 @@ import { VideoActionsDisplayType, VideoDownloadComponent } from '@app/shared/sha
25import { VideoPlaylist, VideoPlaylistService } from '@app/shared/shared-video-playlist' 25import { VideoPlaylist, VideoPlaylistService } from '@app/shared/shared-video-playlist'
26import { MetaService } from '@ngx-meta/core' 26import { MetaService } from '@ngx-meta/core'
27import { peertubeLocalStorage } from '@root-helpers/peertube-web-storage' 27import { peertubeLocalStorage } from '@root-helpers/peertube-web-storage'
28import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes'
28import { ServerConfig, ServerErrorCode, UserVideoRateType, VideoCaption, VideoPrivacy, VideoState } from '@shared/models' 29import { ServerConfig, ServerErrorCode, UserVideoRateType, VideoCaption, VideoPrivacy, VideoState } from '@shared/models'
29import { getStoredP2PEnabled, getStoredTheater } from '../../../assets/player/peertube-player-local-storage' 30import { getStoredP2PEnabled, getStoredTheater } from '../../../assets/player/peertube-player-local-storage'
30import { 31import {
@@ -39,7 +40,6 @@ import { isWebRTCDisabled, timeToInt } from '../../../assets/player/utils'
39import { environment } from '../../../environments/environment' 40import { environment } from '../../../environments/environment'
40import { VideoSupportComponent } from './modal/video-support.component' 41import { VideoSupportComponent } from './modal/video-support.component'
41import { VideoWatchPlaylistComponent } from './video-watch-playlist.component' 42import { VideoWatchPlaylistComponent } from './video-watch-playlist.component'
42import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes'
43 43
44type URLOptions = CustomizationOptions & { playerMode: PlayerMode } 44type URLOptions = CustomizationOptions & { playerMode: PlayerMode }
45 45
@@ -866,21 +866,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
866 866
867 private async subscribeToLiveEventsIfNeeded (oldVideo: VideoDetails, newVideo: VideoDetails) { 867 private async subscribeToLiveEventsIfNeeded (oldVideo: VideoDetails, newVideo: VideoDetails) {
868 if (!this.liveVideosSub) { 868 if (!this.liveVideosSub) {
869 this.liveVideosSub = this.peertubeSocket.getLiveVideosObservable() 869 this.liveVideosSub = this.buildLiveEventsSubscription()
870 .subscribe(({ payload }) => {
871 if (payload.state !== VideoState.PUBLISHED) return
872
873 const videoState = this.video.state.id
874 if (videoState !== VideoState.WAITING_FOR_LIVE && videoState !== VideoState.LIVE_ENDED) return
875
876 console.log('Loading video after live update.')
877
878 const videoUUID = this.video.uuid
879
880 // Reset to refetch the video
881 this.video = undefined
882 this.loadVideo(videoUUID)
883 })
884 } 870 }
885 871
886 if (oldVideo && oldVideo.id !== newVideo.id) { 872 if (oldVideo && oldVideo.id !== newVideo.id) {
@@ -892,6 +878,38 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
892 await this.peertubeSocket.subscribeToLiveVideosSocket(newVideo.id) 878 await this.peertubeSocket.subscribeToLiveVideosSocket(newVideo.id)
893 } 879 }
894 880
881 private buildLiveEventsSubscription () {
882 return this.peertubeSocket.getLiveVideosObservable()
883 .subscribe(({ type, payload }) => {
884 if (type === 'state-change') return this.handleLiveStateChange(payload.state)
885 if (type === 'views-change') return this.handleLiveViewsChange(payload.views)
886 })
887 }
888
889 private handleLiveStateChange (newState: VideoState) {
890 if (newState !== VideoState.PUBLISHED) return
891
892 const videoState = this.video.state.id
893 if (videoState !== VideoState.WAITING_FOR_LIVE && videoState !== VideoState.LIVE_ENDED) return
894
895 console.log('Loading video after live update.')
896
897 const videoUUID = this.video.uuid
898
899 // Reset to refetch the video
900 this.video = undefined
901 this.loadVideo(videoUUID)
902 }
903
904 private handleLiveViewsChange (newViews: number) {
905 if (!this.video) {
906 console.error('Cannot update video live views because video is no defined.')
907 return
908 }
909
910 this.video.views = newViews
911 }
912
895 private initHotkeys () { 913 private initHotkeys () {
896 this.hotkeys = [ 914 this.hotkeys = [
897 // These hotkeys are managed by the player 915 // These hotkeys are managed by the player
diff --git a/client/src/app/core/notification/peertube-socket.service.ts b/client/src/app/core/notification/peertube-socket.service.ts
index 7e1c43364..089276cfc 100644
--- a/client/src/app/core/notification/peertube-socket.service.ts
+++ b/client/src/app/core/notification/peertube-socket.service.ts
@@ -73,8 +73,11 @@ export class PeerTubeSocket {
73 this.liveVideosSocket = this.io(environment.apiUrl + '/live-videos') 73 this.liveVideosSocket = this.io(environment.apiUrl + '/live-videos')
74 }) 74 })
75 75
76 const type: LiveVideoEventType = 'state-change' 76 const types: LiveVideoEventType[] = [ 'views-change', 'state-change' ]
77 this.liveVideosSocket.on(type, (payload: LiveVideoEventPayload) => this.dispatchLiveVideoEvent(type, payload)) 77
78 for (const type of types) {
79 this.liveVideosSocket.on(type, (payload: LiveVideoEventPayload) => this.dispatchLiveVideoEvent(type, payload))
80 }
78 } 81 }
79 82
80 private async importIOIfNeeded () { 83 private async importIOIfNeeded () {
diff --git a/server/lib/activitypub/videos.ts b/server/lib/activitypub/videos.ts
index cb462e258..8545e5bad 100644
--- a/server/lib/activitypub/videos.ts
+++ b/server/lib/activitypub/videos.ts
@@ -461,8 +461,13 @@ async function updateVideoFromAP (options: {
461 transaction: undefined 461 transaction: undefined
462 }) 462 })
463 463
464 if (wasPrivateVideo || wasUnlistedVideo) Notifier.Instance.notifyOnNewVideoIfNeeded(videoUpdated) // Notify our users? 464 // Notify our users?
465 if (videoUpdated.isLive) PeerTubeSocket.Instance.sendVideoLiveNewState(videoUpdated) 465 if (wasPrivateVideo || wasUnlistedVideo) Notifier.Instance.notifyOnNewVideoIfNeeded(videoUpdated)
466
467 if (videoUpdated.isLive) {
468 PeerTubeSocket.Instance.sendVideoLiveNewState(videoUpdated)
469 PeerTubeSocket.Instance.sendVideoViewsUpdate(videoUpdated)
470 }
466 471
467 logger.info('Remote video with uuid %s updated', videoObject.uuid) 472 logger.info('Remote video with uuid %s updated', videoObject.uuid)
468 473
diff --git a/server/lib/job-queue/handlers/video-live-ending.ts b/server/lib/job-queue/handlers/video-live-ending.ts
index 93d925830..8018e2277 100644
--- a/server/lib/job-queue/handlers/video-live-ending.ts
+++ b/server/lib/job-queue/handlers/video-live-ending.ts
@@ -91,7 +91,7 @@ async function saveLive (video: MVideo, live: MVideoLive) {
91 await VideoFileModel.removeHLSFilesOfVideoId(hlsPlaylist.id) 91 await VideoFileModel.removeHLSFilesOfVideoId(hlsPlaylist.id)
92 hlsPlaylist.VideoFiles = [] 92 hlsPlaylist.VideoFiles = []
93 93
94 let durationDone: boolean 94 let durationDone = false
95 95
96 for (const playlistFile of playlistFiles) { 96 for (const playlistFile of playlistFiles) {
97 const concatenatedTsFile = LiveManager.Instance.buildConcatenatedName(playlistFile) 97 const concatenatedTsFile = LiveManager.Instance.buildConcatenatedName(playlistFile)
diff --git a/server/lib/live-manager.ts b/server/lib/live-manager.ts
index 5d9b68756..2fb4b774c 100644
--- a/server/lib/live-manager.ts
+++ b/server/lib/live-manager.ts
@@ -537,6 +537,8 @@ class LiveManager {
537 537
538 await federateVideoIfNeeded(video, false) 538 await federateVideoIfNeeded(video, false)
539 539
540 PeerTubeSocket.Instance.sendVideoViewsUpdate(video)
541
540 // Only keep not expired watchers 542 // Only keep not expired watchers
541 const newWatchers = watchers.filter(w => w > notBefore) 543 const newWatchers = watchers.filter(w => w > notBefore)
542 this.watchersPerVideo.set(videoId, newWatchers) 544 this.watchersPerVideo.set(videoId, newWatchers)
diff --git a/server/lib/peertube-socket.ts b/server/lib/peertube-socket.ts
index 5fc5bc20b..e27963e60 100644
--- a/server/lib/peertube-socket.ts
+++ b/server/lib/peertube-socket.ts
@@ -69,7 +69,18 @@ class PeerTubeSocket {
69 const data: LiveVideoEventPayload = { state: video.state } 69 const data: LiveVideoEventPayload = { state: video.state }
70 const type: LiveVideoEventType = 'state-change' 70 const type: LiveVideoEventType = 'state-change'
71 71
72 logger.debug('Sending video live new state notification of %s.', video.url) 72 logger.debug('Sending video live new state notification of %s.', video.url, { state: video.state })
73
74 this.liveVideosNamespace
75 .in(video.id)
76 .emit(type, data)
77 }
78
79 sendVideoViewsUpdate (video: MVideo) {
80 const data: LiveVideoEventPayload = { views: video.views }
81 const type: LiveVideoEventType = 'views-change'
82
83 logger.debug('Sending video live views update notification of %s.', video.url, { views: video.views })
73 84
74 this.liveVideosNamespace 85 this.liveVideosNamespace
75 .in(video.id) 86 .in(video.id)
diff --git a/server/tests/api/live/live.ts b/server/tests/api/live/live.ts
index e728fcce0..6d504f742 100644
--- a/server/tests/api/live/live.ts
+++ b/server/tests/api/live/live.ts
@@ -328,7 +328,7 @@ describe('Test live', function () {
328 await checkResolutionsInMasterPlaylist(hlsPlaylist.playlistUrl, resolutions) 328 await checkResolutionsInMasterPlaylist(hlsPlaylist.playlistUrl, resolutions)
329 329
330 for (let i = 0; i < resolutions.length; i++) { 330 for (let i = 0; i < resolutions.length; i++) {
331 const segmentNum = 2 331 const segmentNum = 3
332 const segmentName = `${i}-00000${segmentNum}.ts` 332 const segmentName = `${i}-00000${segmentNum}.ts`
333 await waitUntilLiveSegmentGeneration(servers[0], video.uuid, i, segmentNum) 333 await waitUntilLiveSegmentGeneration(servers[0], video.uuid, i, segmentNum)
334 334
@@ -608,6 +608,55 @@ describe('Test live', function () {
608 } 608 }
609 }) 609 })
610 610
611 it('Should correctly send views change notification', async function () {
612 this.timeout(60000)
613
614 let localLastVideoViews = 0
615 let remoteLastVideoViews = 0
616
617 const liveVideoUUID = await createLiveWrapper()
618 await waitJobs(servers)
619
620 {
621 const videoId = await getVideoIdFromUUID(servers[0].url, liveVideoUUID)
622
623 const localSocket = getLiveNotificationSocket(servers[0].url)
624 localSocket.on('views-change', data => { localLastVideoViews = data.views })
625 localSocket.emit('subscribe', { videoId })
626 }
627
628 {
629 const videoId = await getVideoIdFromUUID(servers[1].url, liveVideoUUID)
630
631 const remoteSocket = getLiveNotificationSocket(servers[1].url)
632 remoteSocket.on('views-change', data => { remoteLastVideoViews = data.views })
633 remoteSocket.emit('subscribe', { videoId })
634 }
635
636 const command = await sendRTMPStreamInVideo(servers[0].url, servers[0].accessToken, liveVideoUUID)
637
638 for (const server of servers) {
639 await waitUntilLivePublished(server.url, server.accessToken, liveVideoUUID)
640 }
641
642 await waitJobs(servers)
643
644 expect(localLastVideoViews).to.equal(0)
645 expect(remoteLastVideoViews).to.equal(0)
646
647 await viewVideo(servers[0].url, liveVideoUUID)
648 await viewVideo(servers[1].url, liveVideoUUID)
649
650 await waitJobs(servers)
651 await wait(5000)
652 await waitJobs(servers)
653
654 expect(localLastVideoViews).to.equal(2)
655 expect(remoteLastVideoViews).to.equal(2)
656
657 await stopFfmpeg(command)
658 })
659
611 it('Should not receive a notification after unsubscribe', async function () { 660 it('Should not receive a notification after unsubscribe', async function () {
612 this.timeout(60000) 661 this.timeout(60000)
613 662
diff --git a/shared/models/videos/live/live-video-event-payload.model.ts b/shared/models/videos/live/live-video-event-payload.model.ts
index f9038f4de..6cd7540e8 100644
--- a/shared/models/videos/live/live-video-event-payload.model.ts
+++ b/shared/models/videos/live/live-video-event-payload.model.ts
@@ -1,5 +1,6 @@
1import { VideoState } from '../video-state.enum' 1import { VideoState } from '../video-state.enum'
2 2
3export interface LiveVideoEventPayload { 3export interface LiveVideoEventPayload {
4 state: VideoState 4 state?: VideoState
5 views?: number
5} 6}
diff --git a/shared/models/videos/live/live-video-event.type.ts b/shared/models/videos/live/live-video-event.type.ts
index 4d15899da..50f794561 100644
--- a/shared/models/videos/live/live-video-event.type.ts
+++ b/shared/models/videos/live/live-video-event.type.ts
@@ -1 +1 @@
export type LiveVideoEventType = 'state-change' export type LiveVideoEventType = 'state-change' | 'views-change'