aboutsummaryrefslogtreecommitdiffhomepage
path: root/client/src/assets
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2022-04-05 14:03:52 +0200
committerChocobozzz <chocobozzz@cpy.re>2022-04-15 09:49:35 +0200
commit384ba8b77a8e4805c099f5ea12b41c2ca5776e26 (patch)
tree6b517033d9265d283677b85e0f57486e0e7fd8cf /client/src/assets
parentb211106695bb82f6c32e53306081b5262c3d109d (diff)
downloadPeerTube-384ba8b77a8e4805c099f5ea12b41c2ca5776e26.tar.gz
PeerTube-384ba8b77a8e4805c099f5ea12b41c2ca5776e26.tar.zst
PeerTube-384ba8b77a8e4805c099f5ea12b41c2ca5776e26.zip
Support videos stats in client
Diffstat (limited to 'client/src/assets')
-rw-r--r--client/src/assets/images/feather/stats.svg1
-rw-r--r--client/src/assets/player/shared/manager-options/manager-options-builder.ts20
-rw-r--r--client/src/assets/player/shared/peertube/peertube-plugin.ts118
-rw-r--r--client/src/assets/player/types/manager-options.ts6
-rw-r--r--client/src/assets/player/types/peertube-videojs-typings.ts12
5 files changed, 75 insertions, 82 deletions
diff --git a/client/src/assets/images/feather/stats.svg b/client/src/assets/images/feather/stats.svg
new file mode 100644
index 000000000..864167a6c
--- /dev/null
+++ b/client/src/assets/images/feather/stats.svg
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-bar-chart-2"><line x1="18" y1="20" x2="18" y2="10"></line><line x1="12" y1="20" x2="12" y2="4"></line><line x1="6" y1="20" x2="6" y2="14"></line></svg> \ No newline at end of file
diff --git a/client/src/assets/player/shared/manager-options/manager-options-builder.ts b/client/src/assets/player/shared/manager-options/manager-options-builder.ts
index 29e851c1c..e454c719e 100644
--- a/client/src/assets/player/shared/manager-options/manager-options-builder.ts
+++ b/client/src/assets/player/shared/manager-options/manager-options-builder.ts
@@ -32,14 +32,18 @@ export class ManagerOptionsBuilder {
32 peertube: { 32 peertube: {
33 mode: this.mode, 33 mode: this.mode,
34 autoplay, // Use peertube plugin autoplay because we could get the file by webtorrent 34 autoplay, // Use peertube plugin autoplay because we could get the file by webtorrent
35 videoViewUrl: commonOptions.videoViewUrl, 35
36 videoDuration: commonOptions.videoDuration, 36 ...pick(commonOptions, [
37 userWatching: commonOptions.userWatching, 37 'videoViewUrl',
38 subtitle: commonOptions.subtitle, 38 'authorizationHeader',
39 videoCaptions: commonOptions.videoCaptions, 39 'startTime',
40 stopTime: commonOptions.stopTime, 40 'videoDuration',
41 isLive: commonOptions.isLive, 41 'subtitle',
42 videoUUID: commonOptions.videoUUID 42 'videoCaptions',
43 'stopTime',
44 'isLive',
45 'videoUUID'
46 ])
43 } 47 }
44 } 48 }
45 49
diff --git a/client/src/assets/player/shared/peertube/peertube-plugin.ts b/client/src/assets/player/shared/peertube/peertube-plugin.ts
index 1dc3e3de0..8b65903f9 100644
--- a/client/src/assets/player/shared/peertube/peertube-plugin.ts
+++ b/client/src/assets/player/shared/peertube/peertube-plugin.ts
@@ -2,6 +2,7 @@ import debug from 'debug'
2import videojs from 'video.js' 2import videojs from 'video.js'
3import { isMobile } from '@root-helpers/web-browser' 3import { isMobile } from '@root-helpers/web-browser'
4import { timeToInt } from '@shared/core-utils' 4import { timeToInt } from '@shared/core-utils'
5import { VideoView, VideoViewEvent } from '@shared/models/videos'
5import { 6import {
6 getStoredLastSubtitle, 7 getStoredLastSubtitle,
7 getStoredMute, 8 getStoredMute,
@@ -11,7 +12,7 @@ import {
11 saveVideoWatchHistory, 12 saveVideoWatchHistory,
12 saveVolumeInStore 13 saveVolumeInStore
13} from '../../peertube-player-local-storage' 14} from '../../peertube-player-local-storage'
14import { PeerTubePluginOptions, UserWatching, VideoJSCaption } from '../../types' 15import { PeerTubePluginOptions, VideoJSCaption } from '../../types'
15import { SettingsButton } from '../settings/settings-menu-button' 16import { SettingsButton } from '../settings/settings-menu-button'
16 17
17const logger = debug('peertube:player:peertube') 18const logger = debug('peertube:player:peertube')
@@ -20,18 +21,19 @@ const Plugin = videojs.getPlugin('plugin')
20 21
21class PeerTubePlugin extends Plugin { 22class PeerTubePlugin extends Plugin {
22 private readonly videoViewUrl: string 23 private readonly videoViewUrl: string
23 private readonly videoDuration: number 24 private readonly authorizationHeader: string
25
26 private readonly videoUUID: string
27 private readonly startTime: number
28
24 private readonly CONSTANTS = { 29 private readonly CONSTANTS = {
25 USER_WATCHING_VIDEO_INTERVAL: 5000 // Every 5 seconds, notify the user is watching the video 30 USER_VIEW_VIDEO_INTERVAL: 5000 // Every 5 seconds, notify the user is watching the video
26 } 31 }
27 32
28 private videoCaptions: VideoJSCaption[] 33 private videoCaptions: VideoJSCaption[]
29 private defaultSubtitle: string 34 private defaultSubtitle: string
30 35
31 private videoViewInterval: any 36 private videoViewInterval: any
32 private userWatchingVideoInterval: any
33
34 private isLive: boolean
35 37
36 private menuOpened = false 38 private menuOpened = false
37 private mouseInControlBar = false 39 private mouseInControlBar = false
@@ -42,9 +44,11 @@ class PeerTubePlugin extends Plugin {
42 super(player) 44 super(player)
43 45
44 this.videoViewUrl = options.videoViewUrl 46 this.videoViewUrl = options.videoViewUrl
45 this.videoDuration = options.videoDuration 47 this.authorizationHeader = options.authorizationHeader
48 this.videoUUID = options.videoUUID
49 this.startTime = timeToInt(options.startTime)
50
46 this.videoCaptions = options.videoCaptions 51 this.videoCaptions = options.videoCaptions
47 this.isLive = options.isLive
48 this.initialInactivityTimeout = this.player.options_.inactivityTimeout 52 this.initialInactivityTimeout = this.player.options_.inactivityTimeout
49 53
50 if (options.autoplay) this.player.addClass('vjs-has-autoplay') 54 if (options.autoplay) this.player.addClass('vjs-has-autoplay')
@@ -101,15 +105,12 @@ class PeerTubePlugin extends Plugin {
101 this.player.duration(options.videoDuration) 105 this.player.duration(options.videoDuration)
102 106
103 this.initializePlayer() 107 this.initializePlayer()
104 this.runViewAdd() 108 this.runUserViewing()
105
106 this.runUserWatchVideo(options.userWatching, options.videoUUID)
107 }) 109 })
108 } 110 }
109 111
110 dispose () { 112 dispose () {
111 if (this.videoViewInterval) clearInterval(this.videoViewInterval) 113 if (this.videoViewInterval) clearInterval(this.videoViewInterval)
112 if (this.userWatchingVideoInterval) clearInterval(this.userWatchingVideoInterval)
113 } 114 }
114 115
115 onMenuOpened () { 116 onMenuOpened () {
@@ -142,74 +143,65 @@ class PeerTubePlugin extends Plugin {
142 this.listenFullScreenChange() 143 this.listenFullScreenChange()
143 } 144 }
144 145
145 private runViewAdd () { 146 private runUserViewing () {
146 this.clearVideoViewInterval() 147 let lastCurrentTime = this.startTime
148 let lastViewEvent: VideoViewEvent
147 149
148 // After 30 seconds (or 3/4 of the video), add a view to the video 150 this.player.one('play', () => {
149 let minSecondsToView = 30 151 this.notifyUserIsWatching(this.startTime, lastViewEvent)
152 })
150 153
151 if (!this.isLive && this.videoDuration < minSecondsToView) { 154 this.player.on('seeked', () => {
152 minSecondsToView = (this.videoDuration * 3) / 4 155 // Don't take into account small seek events
153 } 156 if (Math.abs(this.player.currentTime() - lastCurrentTime) < 3) return
154 157
155 let secondsViewed = 0 158 lastViewEvent = 'seek'
156 this.videoViewInterval = setInterval(() => { 159 })
157 if (this.player && !this.player.paused()) {
158 secondsViewed += 1
159
160 if (secondsViewed > minSecondsToView) {
161 // Restart the loop if this is a live
162 if (this.isLive) {
163 secondsViewed = 0
164 } else {
165 this.clearVideoViewInterval()
166 }
167 160
168 this.addViewToVideo().catch(err => console.error(err)) 161 this.player.one('ended', () => {
169 } 162 const currentTime = Math.floor(this.player.duration())
170 } 163 lastCurrentTime = currentTime
171 }, 1000)
172 }
173 164
174 private runUserWatchVideo (options: UserWatching, videoUUID: string) { 165 this.notifyUserIsWatching(currentTime, lastViewEvent)
175 let lastCurrentTime = 0
176 166
177 this.userWatchingVideoInterval = setInterval(() => { 167 lastViewEvent = undefined
168 })
169
170 this.videoViewInterval = setInterval(() => {
178 const currentTime = Math.floor(this.player.currentTime()) 171 const currentTime = Math.floor(this.player.currentTime())
179 172
180 if (currentTime - lastCurrentTime >= 1) { 173 // No need to update
181 lastCurrentTime = currentTime 174 if (currentTime === lastCurrentTime) return
182 175
183 if (options) { 176 lastCurrentTime = currentTime
184 this.notifyUserIsWatching(currentTime, options.url, options.authorizationHeader)
185 .catch(err => console.error('Cannot notify user is watching.', err))
186 } else {
187 saveVideoWatchHistory(videoUUID, currentTime)
188 }
189 }
190 }, this.CONSTANTS.USER_WATCHING_VIDEO_INTERVAL)
191 }
192 177
193 private clearVideoViewInterval () { 178 this.notifyUserIsWatching(currentTime, lastViewEvent)
194 if (this.videoViewInterval !== undefined) { 179 .catch(err => console.error('Cannot notify user is watching.', err))
195 clearInterval(this.videoViewInterval) 180
196 this.videoViewInterval = undefined 181 lastViewEvent = undefined
197 } 182
183 // Server won't save history, so save the video position in local storage
184 if (!this.authorizationHeader) {
185 saveVideoWatchHistory(this.videoUUID, currentTime)
186 }
187 }, this.CONSTANTS.USER_VIEW_VIDEO_INTERVAL)
198 } 188 }
199 189
200 private addViewToVideo () { 190 private notifyUserIsWatching (currentTime: number, viewEvent: VideoViewEvent) {
201 if (!this.videoViewUrl) return Promise.resolve(undefined) 191 if (!this.videoViewUrl) return Promise.resolve(undefined)
202 192
203 return fetch(this.videoViewUrl, { method: 'POST' }) 193 const body: VideoView = {
204 } 194 currentTime,
195 viewEvent
196 }
205 197
206 private notifyUserIsWatching (currentTime: number, url: string, authorizationHeader: string) { 198 const headers = new Headers({
207 const body = new URLSearchParams() 199 'Content-type': 'application/json; charset=UTF-8'
208 body.append('currentTime', currentTime.toString()) 200 })
209 201
210 const headers = new Headers({ Authorization: authorizationHeader }) 202 if (this.authorizationHeader) headers.set('Authorization', this.authorizationHeader)
211 203
212 return fetch(url, { method: 'PUT', body, headers }) 204 return fetch(this.videoViewUrl, { method: 'POST', body: JSON.stringify(body), headers })
213 } 205 }
214 206
215 private listenFullScreenChange () { 207 private listenFullScreenChange () {
diff --git a/client/src/assets/player/types/manager-options.ts b/client/src/assets/player/types/manager-options.ts
index b3ad7e337..456ef115e 100644
--- a/client/src/assets/player/types/manager-options.ts
+++ b/client/src/assets/player/types/manager-options.ts
@@ -1,6 +1,6 @@
1import { PluginsManager } from '@root-helpers/plugins-manager' 1import { PluginsManager } from '@root-helpers/plugins-manager'
2import { LiveVideoLatencyMode, VideoFile } from '@shared/models' 2import { LiveVideoLatencyMode, VideoFile } from '@shared/models'
3import { PlaylistPluginOptions, UserWatching, VideoJSCaption } from './peertube-videojs-typings' 3import { PlaylistPluginOptions, VideoJSCaption } from './peertube-videojs-typings'
4 4
5export type PlayerMode = 'webtorrent' | 'p2p-media-loader' 5export type PlayerMode = 'webtorrent' | 'p2p-media-loader'
6 6
@@ -53,6 +53,8 @@ export interface CommonOptions extends CustomizationOptions {
53 captions: boolean 53 captions: boolean
54 54
55 videoViewUrl: string 55 videoViewUrl: string
56 authorizationHeader?: string
57
56 embedUrl: string 58 embedUrl: string
57 embedTitle: string 59 embedTitle: string
58 60
@@ -68,8 +70,6 @@ export interface CommonOptions extends CustomizationOptions {
68 videoUUID: string 70 videoUUID: string
69 videoShortUUID: string 71 videoShortUUID: string
70 72
71 userWatching?: UserWatching
72
73 serverUrl: string 73 serverUrl: string
74 74
75 errorNotifier: (message: string) => void 75 errorNotifier: (message: string) => void
diff --git a/client/src/assets/player/types/peertube-videojs-typings.ts b/client/src/assets/player/types/peertube-videojs-typings.ts
index d9a388681..ad284a671 100644
--- a/client/src/assets/player/types/peertube-videojs-typings.ts
+++ b/client/src/assets/player/types/peertube-videojs-typings.ts
@@ -88,23 +88,20 @@ type VideoJSCaption = {
88 src: string 88 src: string
89} 89}
90 90
91type UserWatching = {
92 url: string
93 authorizationHeader: string
94}
95
96type PeerTubePluginOptions = { 91type PeerTubePluginOptions = {
97 mode: PlayerMode 92 mode: PlayerMode
98 93
99 autoplay: boolean 94 autoplay: boolean
100 videoViewUrl: string
101 videoDuration: number 95 videoDuration: number
102 96
103 userWatching?: UserWatching 97 videoViewUrl: string
98 authorizationHeader?: string
99
104 subtitle?: string 100 subtitle?: string
105 101
106 videoCaptions: VideoJSCaption[] 102 videoCaptions: VideoJSCaption[]
107 103
104 startTime: number | string
108 stopTime: number | string 105 stopTime: number | string
109 106
110 isLive: boolean 107 isLive: boolean
@@ -230,7 +227,6 @@ export {
230 AutoResolutionUpdateData, 227 AutoResolutionUpdateData,
231 PlaylistPluginOptions, 228 PlaylistPluginOptions,
232 VideoJSCaption, 229 VideoJSCaption,
233 UserWatching,
234 PeerTubePluginOptions, 230 PeerTubePluginOptions,
235 WebtorrentPluginOptions, 231 WebtorrentPluginOptions,
236 P2PMediaLoaderPluginOptions, 232 P2PMediaLoaderPluginOptions,