diff options
Diffstat (limited to 'client')
-rw-r--r-- | client/package.json | 4 | ||||
-rw-r--r-- | client/src/app/videos/+video-watch/modal/video-share.component.ts | 6 | ||||
-rw-r--r-- | client/src/app/videos/+video-watch/video-watch.component.ts | 1 | ||||
-rw-r--r-- | client/src/assets/player/peertube-link-button.ts | 20 | ||||
-rw-r--r-- | client/src/assets/player/peertube-player.ts | 27 | ||||
-rw-r--r-- | client/src/assets/player/peertube-videojs-plugin.ts | 4 | ||||
-rw-r--r-- | client/src/assets/player/utils.ts | 34 | ||||
-rw-r--r-- | client/src/assets/player/video-renderer.ts | 6 | ||||
-rw-r--r-- | client/src/sass/video-js-custom.scss | 30 | ||||
-rw-r--r-- | client/src/standalone/videos/embed.ts | 1 | ||||
-rw-r--r-- | client/yarn.lock | 15 |
11 files changed, 127 insertions, 21 deletions
diff --git a/client/package.json b/client/package.json index 0d5acf997..61f94758a 100644 --- a/client/package.json +++ b/client/package.json | |||
@@ -23,7 +23,7 @@ | |||
23 | }, | 23 | }, |
24 | "license": "GPLv3", | 24 | "license": "GPLv3", |
25 | "resolutions": { | 25 | "resolutions": { |
26 | "videojs-dock/video.js": "^7", | 26 | "video.js": "^7", |
27 | "webtorrent/create-torrent/junk": "^1", | 27 | "webtorrent/create-torrent/junk": "^1", |
28 | "simple-get": "^2.8.1" | 28 | "simple-get": "^2.8.1" |
29 | }, | 29 | }, |
@@ -99,6 +99,8 @@ | |||
99 | "typescript": "2.7", | 99 | "typescript": "2.7", |
100 | "uglifyjs-webpack-plugin": "^1.1.2", | 100 | "uglifyjs-webpack-plugin": "^1.1.2", |
101 | "video.js": "^7.0.3", | 101 | "video.js": "^7.0.3", |
102 | "videojs-contextmenu": "^2.0.0", | ||
103 | "videojs-contextmenu-ui": "^4.0.0", | ||
102 | "videojs-dock": "^2.0.2", | 104 | "videojs-dock": "^2.0.2", |
103 | "videojs-hotkeys": "^0.2.21", | 105 | "videojs-hotkeys": "^0.2.21", |
104 | "webpack": "^4.5.0", | 106 | "webpack": "^4.5.0", |
diff --git a/client/src/app/videos/+video-watch/modal/video-share.component.ts b/client/src/app/videos/+video-watch/modal/video-share.component.ts index 678cccfb5..33998c5d8 100644 --- a/client/src/app/videos/+video-watch/modal/video-share.component.ts +++ b/client/src/app/videos/+video-watch/modal/video-share.component.ts | |||
@@ -4,6 +4,7 @@ import { NotificationsService } from 'angular2-notifications' | |||
4 | 4 | ||
5 | import { ModalDirective } from 'ngx-bootstrap/modal' | 5 | import { ModalDirective } from 'ngx-bootstrap/modal' |
6 | import { VideoDetails } from '../../../shared/video/video-details.model' | 6 | import { VideoDetails } from '../../../shared/video/video-details.model' |
7 | import { buildVideoEmbed } from '../../../../assets/player/utils' | ||
7 | 8 | ||
8 | @Component({ | 9 | @Component({ |
9 | selector: 'my-video-share', | 10 | selector: 'my-video-share', |
@@ -28,10 +29,7 @@ export class VideoShareComponent { | |||
28 | } | 29 | } |
29 | 30 | ||
30 | getVideoIframeCode () { | 31 | getVideoIframeCode () { |
31 | return '<iframe width="560" height="315" ' + | 32 | return buildVideoEmbed(this.video.embedUrl) |
32 | 'src="' + this.video.embedUrl + '" ' + | ||
33 | 'frameborder="0" allowfullscreen>' + | ||
34 | '</iframe>' | ||
35 | } | 33 | } |
36 | 34 | ||
37 | getVideoUrl () { | 35 | getVideoUrl () { |
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 35a7c04cc..c71051649 100644 --- a/client/src/app/videos/+video-watch/video-watch.component.ts +++ b/client/src/app/videos/+video-watch/video-watch.component.ts | |||
@@ -348,6 +348,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
348 | inactivityTimeout: 2500, | 348 | inactivityTimeout: 2500, |
349 | videoFiles: this.video.files, | 349 | videoFiles: this.video.files, |
350 | playerElement: this.playerElement, | 350 | playerElement: this.playerElement, |
351 | videoEmbedUrl: this.video.embedUrl, | ||
351 | videoViewUrl: this.videoService.getVideoViewUrl(this.video.uuid), | 352 | videoViewUrl: this.videoService.getVideoViewUrl(this.video.uuid), |
352 | videoDuration: this.video.duration, | 353 | videoDuration: this.video.duration, |
353 | enableHotkeys: true, | 354 | enableHotkeys: true, |
diff --git a/client/src/assets/player/peertube-link-button.ts b/client/src/assets/player/peertube-link-button.ts index 54d802732..a13815d61 100644 --- a/client/src/assets/player/peertube-link-button.ts +++ b/client/src/assets/player/peertube-link-button.ts | |||
@@ -1,15 +1,19 @@ | |||
1 | import { VideoJSComponentInterface, videojsUntyped } from './peertube-videojs-typings' | 1 | import { VideoJSComponentInterface, videojsUntyped } from './peertube-videojs-typings' |
2 | import { buildVideoLink } from './utils' | ||
2 | 3 | ||
3 | const Button: VideoJSComponentInterface = videojsUntyped.getComponent('Button') | 4 | const Button: VideoJSComponentInterface = videojsUntyped.getComponent('Button') |
4 | class PeerTubeLinkButton extends Button { | 5 | class PeerTubeLinkButton extends Button { |
5 | 6 | ||
7 | constructor (player: videojs.Player, options) { | ||
8 | super(player, options) | ||
9 | } | ||
10 | |||
6 | createEl () { | 11 | createEl () { |
7 | return this.buildElement() | 12 | return this.buildElement() |
8 | } | 13 | } |
9 | 14 | ||
10 | updateHref () { | 15 | updateHref () { |
11 | const currentTime = Math.floor(this.player().currentTime()) | 16 | this.el().setAttribute('href', buildVideoLink(this.player().currentTime())) |
12 | this.el().setAttribute('href', this.buildHref(currentTime)) | ||
13 | } | 17 | } |
14 | 18 | ||
15 | handleClick () { | 19 | handleClick () { |
@@ -18,7 +22,7 @@ class PeerTubeLinkButton extends Button { | |||
18 | 22 | ||
19 | private buildElement () { | 23 | private buildElement () { |
20 | const el = videojsUntyped.dom.createEl('a', { | 24 | const el = videojsUntyped.dom.createEl('a', { |
21 | href: this.buildHref(), | 25 | href: buildVideoLink(), |
22 | innerHTML: 'PeerTube', | 26 | innerHTML: 'PeerTube', |
23 | title: 'Go to the video page', | 27 | title: 'Go to the video page', |
24 | className: 'vjs-peertube-link', | 28 | className: 'vjs-peertube-link', |
@@ -29,15 +33,5 @@ class PeerTubeLinkButton extends Button { | |||
29 | 33 | ||
30 | return el | 34 | return el |
31 | } | 35 | } |
32 | |||
33 | private buildHref (time?: number) { | ||
34 | let href = window.location.href.replace('embed', 'watch') | ||
35 | if (time) { | ||
36 | if (window.location.search) href += '&start=' + time | ||
37 | else href += '?start=' + time | ||
38 | } | ||
39 | |||
40 | return href | ||
41 | } | ||
42 | } | 36 | } |
43 | Button.registerComponent('PeerTubeLinkButton', PeerTubeLinkButton) | 37 | Button.registerComponent('PeerTubeLinkButton', PeerTubeLinkButton) |
diff --git a/client/src/assets/player/peertube-player.ts b/client/src/assets/player/peertube-player.ts index f419d58fc..9fe5af569 100644 --- a/client/src/assets/player/peertube-player.ts +++ b/client/src/assets/player/peertube-player.ts | |||
@@ -2,12 +2,15 @@ import { VideoFile } from '../../../../shared/models/videos' | |||
2 | 2 | ||
3 | import 'videojs-hotkeys' | 3 | import 'videojs-hotkeys' |
4 | import 'videojs-dock' | 4 | import 'videojs-dock' |
5 | import 'videojs-contextmenu' | ||
6 | import 'videojs-contextmenu-ui' | ||
5 | import './peertube-link-button' | 7 | import './peertube-link-button' |
6 | import './resolution-menu-button' | 8 | import './resolution-menu-button' |
7 | import './settings-menu-button' | 9 | import './settings-menu-button' |
8 | import './webtorrent-info-button' | 10 | import './webtorrent-info-button' |
9 | import './peertube-videojs-plugin' | 11 | import './peertube-videojs-plugin' |
10 | import { videojsUntyped } from './peertube-videojs-typings' | 12 | import { videojsUntyped } from './peertube-videojs-typings' |
13 | import { buildVideoEmbed, buildVideoLink, copyToClipboard } from './utils' | ||
11 | 14 | ||
12 | // Change 'Playback Rate' to 'Speed' (smaller for our settings menu) | 15 | // Change 'Playback Rate' to 'Speed' (smaller for our settings menu) |
13 | videojsUntyped.getComponent('PlaybackRateMenuButton').prototype.controlText_ = 'Speed' | 16 | videojsUntyped.getComponent('PlaybackRateMenuButton').prototype.controlText_ = 'Speed' |
@@ -16,6 +19,7 @@ function getVideojsOptions (options: { | |||
16 | autoplay: boolean, | 19 | autoplay: boolean, |
17 | playerElement: HTMLVideoElement, | 20 | playerElement: HTMLVideoElement, |
18 | videoViewUrl: string, | 21 | videoViewUrl: string, |
22 | videoEmbedUrl: string, | ||
19 | videoDuration: number, | 23 | videoDuration: number, |
20 | videoFiles: VideoFile[], | 24 | videoFiles: VideoFile[], |
21 | enableHotkeys: boolean, | 25 | enableHotkeys: boolean, |
@@ -38,6 +42,29 @@ function getVideojsOptions (options: { | |||
38 | videoViewUrl: options.videoViewUrl, | 42 | videoViewUrl: options.videoViewUrl, |
39 | videoDuration: options.videoDuration, | 43 | videoDuration: options.videoDuration, |
40 | startTime: options.startTime | 44 | startTime: options.startTime |
45 | }, | ||
46 | contextmenuUI: { | ||
47 | content: [ | ||
48 | { | ||
49 | label: 'Copy the video URL', | ||
50 | listener: function () { | ||
51 | copyToClipboard(buildVideoLink()) | ||
52 | } | ||
53 | }, | ||
54 | { | ||
55 | label: 'Copy the video URL at the current time', | ||
56 | listener: function () { | ||
57 | const player = this | ||
58 | copyToClipboard(buildVideoLink(player.currentTime())) | ||
59 | } | ||
60 | }, | ||
61 | { | ||
62 | label: 'Copy embed code', | ||
63 | listener: () => { | ||
64 | copyToClipboard(buildVideoEmbed(options.videoEmbedUrl)) | ||
65 | } | ||
66 | } | ||
67 | ] | ||
41 | } | 68 | } |
42 | }, | 69 | }, |
43 | controlBar: { | 70 | controlBar: { |
diff --git a/client/src/assets/player/peertube-videojs-plugin.ts b/client/src/assets/player/peertube-videojs-plugin.ts index d9ded7a7e..65103f3ab 100644 --- a/client/src/assets/player/peertube-videojs-plugin.ts +++ b/client/src/assets/player/peertube-videojs-plugin.ts | |||
@@ -268,6 +268,10 @@ class PeerTubePlugin extends Plugin { | |||
268 | this.trigger('autoResolutionUpdate') | 268 | this.trigger('autoResolutionUpdate') |
269 | } | 269 | } |
270 | 270 | ||
271 | getCurrentVideoFile () { | ||
272 | return this.currentVideoFile | ||
273 | } | ||
274 | |||
271 | private tryToPlay (done?: Function) { | 275 | private tryToPlay (done?: Function) { |
272 | if (!done) done = function () { /* empty */ } | 276 | if (!done) done = function () { /* empty */ } |
273 | 277 | ||
diff --git a/client/src/assets/player/utils.ts b/client/src/assets/player/utils.ts index 1df39d4e4..487b3a1be 100644 --- a/client/src/assets/player/utils.ts +++ b/client/src/assets/player/utils.ts | |||
@@ -64,14 +64,48 @@ function isMobile () { | |||
64 | return /iPhone|iPad|iPod|Android/i.test(navigator.userAgent) | 64 | return /iPhone|iPad|iPod|Android/i.test(navigator.userAgent) |
65 | } | 65 | } |
66 | 66 | ||
67 | function buildVideoLink (time?: number) { | ||
68 | let href = window.location.href.replace('/embed/', '/watch/') | ||
69 | if (time) { | ||
70 | const timeInt = Math.floor(time) | ||
71 | |||
72 | if (window.location.search) href += '&start=' + timeInt | ||
73 | else href += '?start=' + timeInt | ||
74 | } | ||
75 | |||
76 | return href | ||
77 | } | ||
78 | |||
79 | function buildVideoEmbed (embedUrl: string) { | ||
80 | return '<iframe width="560" height="315" ' + | ||
81 | 'src="' + embedUrl + '" ' + | ||
82 | 'frameborder="0" allowfullscreen>' + | ||
83 | '</iframe>' | ||
84 | } | ||
85 | |||
86 | function copyToClipboard (text: string) { | ||
87 | const el = document.createElement('textarea') | ||
88 | el.value = text | ||
89 | el.setAttribute('readonly', '') | ||
90 | el.style.position = 'absolute' | ||
91 | el.style.left = '-9999px' | ||
92 | document.body.appendChild(el) | ||
93 | el.select() | ||
94 | document.execCommand('copy') | ||
95 | document.body.removeChild(el) | ||
96 | } | ||
97 | |||
67 | export { | 98 | export { |
68 | toTitleCase, | 99 | toTitleCase, |
100 | buildVideoLink, | ||
69 | getStoredVolume, | 101 | getStoredVolume, |
70 | saveVolumeInStore, | 102 | saveVolumeInStore, |
71 | saveAverageBandwidth, | 103 | saveAverageBandwidth, |
72 | getAverageBandwidth, | 104 | getAverageBandwidth, |
73 | saveMuteInStore, | 105 | saveMuteInStore, |
106 | buildVideoEmbed, | ||
74 | getStoredMute, | 107 | getStoredMute, |
108 | copyToClipboard, | ||
75 | isMobile, | 109 | isMobile, |
76 | bytes | 110 | bytes |
77 | } | 111 | } |
diff --git a/client/src/assets/player/video-renderer.ts b/client/src/assets/player/video-renderer.ts index 4b54b661a..4affb43cf 100644 --- a/client/src/assets/player/video-renderer.ts +++ b/client/src/assets/player/video-renderer.ts | |||
@@ -50,7 +50,7 @@ function renderMedia (file, elem: HTMLVideoElement, opts: RenderMediaOptions, ca | |||
50 | 50 | ||
51 | return fallbackToMediaSource() | 51 | return fallbackToMediaSource() |
52 | }) | 52 | }) |
53 | preparedElem.addEventListener('canplay', onLoadStart) | 53 | preparedElem.addEventListener('loadstart', onLoadStart) |
54 | return videostream(file, preparedElem) | 54 | return videostream(file, preparedElem) |
55 | } | 55 | } |
56 | 56 | ||
@@ -66,7 +66,7 @@ function renderMedia (file, elem: HTMLVideoElement, opts: RenderMediaOptions, ca | |||
66 | 66 | ||
67 | return callback(err) | 67 | return callback(err) |
68 | }) | 68 | }) |
69 | preparedElem.addEventListener('canplay', onLoadStart) | 69 | preparedElem.addEventListener('loadstart', onLoadStart) |
70 | 70 | ||
71 | const wrapper = new MediaElementWrapper(preparedElem) | 71 | const wrapper = new MediaElementWrapper(preparedElem) |
72 | const writable = wrapper.createWriteStream(codecs) | 72 | const writable = wrapper.createWriteStream(codecs) |
@@ -95,7 +95,7 @@ function renderMedia (file, elem: HTMLVideoElement, opts: RenderMediaOptions, ca | |||
95 | } | 95 | } |
96 | 96 | ||
97 | function onLoadStart () { | 97 | function onLoadStart () { |
98 | preparedElem.removeEventListener('canplay', onLoadStart) | 98 | preparedElem.removeEventListener('loadstart', onLoadStart) |
99 | if (opts.autoplay) preparedElem.play() | 99 | if (opts.autoplay) preparedElem.play() |
100 | 100 | ||
101 | callback(null, renderer) | 101 | callback(null, renderer) |
diff --git a/client/src/sass/video-js-custom.scss b/client/src/sass/video-js-custom.scss index 680958a9f..350e7cdd5 100644 --- a/client/src/sass/video-js-custom.scss +++ b/client/src/sass/video-js-custom.scss | |||
@@ -21,6 +21,8 @@ $slider-bg-color: lighten($primary-background-color, 33%); | |||
21 | $setting-transition-duration: 0.15s; | 21 | $setting-transition-duration: 0.15s; |
22 | $setting-transition-easing: ease-out; | 22 | $setting-transition-easing: ease-out; |
23 | 23 | ||
24 | $context-menu-width: 350px; | ||
25 | |||
24 | .video-js.vjs-peertube-skin { | 26 | .video-js.vjs-peertube-skin { |
25 | font-size: $font-size; | 27 | font-size: $font-size; |
26 | color: $primary-foreground-color; | 28 | color: $primary-foreground-color; |
@@ -787,4 +789,32 @@ $setting-transition-easing: ease-out; | |||
787 | } | 789 | } |
788 | } | 790 | } |
789 | } | 791 | } |
792 | } | ||
793 | |||
794 | /* Sass for videojs-contextmenu-ui */ | ||
795 | |||
796 | .video-js .vjs-contextmenu-ui-menu { | ||
797 | position: absolute; | ||
798 | background-color: rgba(0, 0, 0, 0.5); | ||
799 | padding: 5px 0; | ||
800 | width: $context-menu-width; | ||
801 | |||
802 | .vjs-menu-content { | ||
803 | opacity: $primary-foreground-opacity; | ||
804 | color: $primary-foreground-color; | ||
805 | font-size: $font-size !important; | ||
806 | font-weight: $font-semibold; | ||
807 | } | ||
808 | |||
809 | .vjs-menu-item { | ||
810 | cursor: pointer; | ||
811 | font-size: 1em; | ||
812 | padding: 8px 16px; | ||
813 | text-align: left; | ||
814 | text-transform: none; | ||
815 | |||
816 | &:hover { | ||
817 | background-color: rgba(255, 255, 255, 0.2); | ||
818 | } | ||
819 | } | ||
790 | } \ No newline at end of file | 820 | } \ No newline at end of file |
diff --git a/client/src/standalone/videos/embed.ts b/client/src/standalone/videos/embed.ts index ba906cc32..d603690ca 100644 --- a/client/src/standalone/videos/embed.ts +++ b/client/src/standalone/videos/embed.ts | |||
@@ -91,6 +91,7 @@ loadVideoInfo(videoId) | |||
91 | const videojsOptions = getVideojsOptions({ | 91 | const videojsOptions = getVideojsOptions({ |
92 | autoplay, | 92 | autoplay, |
93 | inactivityTimeout: 1500, | 93 | inactivityTimeout: 1500, |
94 | videoEmbedUrl: window.location.origin + videoInfo.embedPath, | ||
94 | videoViewUrl: getVideoUrl(videoId) + '/views', | 95 | videoViewUrl: getVideoUrl(videoId) + '/views', |
95 | playerElement: videoElement, | 96 | playerElement: videoElement, |
96 | videoFiles: videoInfo.files, | 97 | videoFiles: videoInfo.files, |
diff --git a/client/yarn.lock b/client/yarn.lock index 7806611f2..fe2e040d8 100644 --- a/client/yarn.lock +++ b/client/yarn.lock | |||
@@ -10006,6 +10006,21 @@ video.js@^5.19.2, "video.js@^6.8.0 || ^7.0.0", video.js@^7, video.js@^7.0.3: | |||
10006 | videojs-vtt.js "0.14.1" | 10006 | videojs-vtt.js "0.14.1" |
10007 | xhr "2.4.0" | 10007 | xhr "2.4.0" |
10008 | 10008 | ||
10009 | videojs-contextmenu-ui@^4.0.0: | ||
10010 | version "4.0.0" | ||
10011 | resolved "https://registry.yarnpkg.com/videojs-contextmenu-ui/-/videojs-contextmenu-ui-4.0.0.tgz#e7ffceacac95c5d2bc7f80db6f75675404de542a" | ||
10012 | dependencies: | ||
10013 | global "^4.3.2" | ||
10014 | video.js "^5.19.2" | ||
10015 | videojs-contextmenu "^2.0.0" | ||
10016 | |||
10017 | videojs-contextmenu@^2.0.0: | ||
10018 | version "2.0.0" | ||
10019 | resolved "https://registry.yarnpkg.com/videojs-contextmenu/-/videojs-contextmenu-2.0.0.tgz#7213c8c420ecd2904d26f19c21085f7ebf496e9e" | ||
10020 | dependencies: | ||
10021 | global "^4.3.2" | ||
10022 | video.js "^5.19.2" | ||
10023 | |||
10009 | videojs-dock@^2.0.2: | 10024 | videojs-dock@^2.0.2: |
10010 | version "2.1.2" | 10025 | version "2.1.2" |
10011 | resolved "https://registry.yarnpkg.com/videojs-dock/-/videojs-dock-2.1.2.tgz#621c27c6f7dd131c541535300ac545377e515a0e" | 10026 | resolved "https://registry.yarnpkg.com/videojs-dock/-/videojs-dock-2.1.2.tgz#621c27c6f7dd131c541535300ac545377e515a0e" |