Add context menu to player

pull/612/head
Chocobozzz 2018-05-30 17:10:00 +02:00
parent e10c7d5125
commit 960a11e89d
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
14 changed files with 145 additions and 127 deletions

View File

@ -22,4 +22,4 @@ rules:
space-before-colon: 1
hex-length: 1
hex-notation: 0
indentation: 1
indentation: 2

View File

@ -23,7 +23,7 @@
},
"license": "GPLv3",
"resolutions": {
"videojs-dock/video.js": "^7",
"video.js": "^7",
"webtorrent/create-torrent/junk": "^1",
"simple-get": "^2.8.1"
},
@ -99,6 +99,8 @@
"typescript": "2.7",
"uglifyjs-webpack-plugin": "^1.1.2",
"video.js": "^7.0.3",
"videojs-contextmenu": "^2.0.0",
"videojs-contextmenu-ui": "^4.0.0",
"videojs-dock": "^2.0.2",
"videojs-hotkeys": "^0.2.21",
"webpack": "^4.5.0",

View File

@ -4,6 +4,7 @@ import { NotificationsService } from 'angular2-notifications'
import { ModalDirective } from 'ngx-bootstrap/modal'
import { VideoDetails } from '../../../shared/video/video-details.model'
import { buildVideoEmbed } from '../../../../assets/player/utils'
@Component({
selector: 'my-video-share',
@ -28,10 +29,7 @@ export class VideoShareComponent {
}
getVideoIframeCode () {
return '<iframe width="560" height="315" ' +
'src="' + this.video.embedUrl + '" ' +
'frameborder="0" allowfullscreen>' +
'</iframe>'
return buildVideoEmbed(this.video.embedUrl)
}
getVideoUrl () {

View File

@ -348,6 +348,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
inactivityTimeout: 2500,
videoFiles: this.video.files,
playerElement: this.playerElement,
videoEmbedUrl: this.video.embedUrl,
videoViewUrl: this.videoService.getVideoViewUrl(this.video.uuid),
videoDuration: this.video.duration,
enableHotkeys: true,

View File

@ -1,15 +1,19 @@
import { VideoJSComponentInterface, videojsUntyped } from './peertube-videojs-typings'
import { buildVideoLink } from './utils'
const Button: VideoJSComponentInterface = videojsUntyped.getComponent('Button')
class PeerTubeLinkButton extends Button {
constructor (player: videojs.Player, options) {
super(player, options)
}
createEl () {
return this.buildElement()
}
updateHref () {
const currentTime = Math.floor(this.player().currentTime())
this.el().setAttribute('href', this.buildHref(currentTime))
this.el().setAttribute('href', buildVideoLink(this.player().currentTime()))
}
handleClick () {
@ -18,7 +22,7 @@ class PeerTubeLinkButton extends Button {
private buildElement () {
const el = videojsUntyped.dom.createEl('a', {
href: this.buildHref(),
href: buildVideoLink(),
innerHTML: 'PeerTube',
title: 'Go to the video page',
className: 'vjs-peertube-link',
@ -29,15 +33,5 @@ class PeerTubeLinkButton extends Button {
return el
}
private buildHref (time?: number) {
let href = window.location.href.replace('embed', 'watch')
if (time) {
if (window.location.search) href += '&start=' + time
else href += '?start=' + time
}
return href
}
}
Button.registerComponent('PeerTubeLinkButton', PeerTubeLinkButton)

View File

@ -2,12 +2,15 @@ import { VideoFile } from '../../../../shared/models/videos'
import 'videojs-hotkeys'
import 'videojs-dock'
import 'videojs-contextmenu'
import 'videojs-contextmenu-ui'
import './peertube-link-button'
import './resolution-menu-button'
import './settings-menu-button'
import './webtorrent-info-button'
import './peertube-videojs-plugin'
import { videojsUntyped } from './peertube-videojs-typings'
import { buildVideoEmbed, buildVideoLink, copyToClipboard } from './utils'
// Change 'Playback Rate' to 'Speed' (smaller for our settings menu)
videojsUntyped.getComponent('PlaybackRateMenuButton').prototype.controlText_ = 'Speed'
@ -16,6 +19,7 @@ function getVideojsOptions (options: {
autoplay: boolean,
playerElement: HTMLVideoElement,
videoViewUrl: string,
videoEmbedUrl: string,
videoDuration: number,
videoFiles: VideoFile[],
enableHotkeys: boolean,
@ -38,6 +42,29 @@ function getVideojsOptions (options: {
videoViewUrl: options.videoViewUrl,
videoDuration: options.videoDuration,
startTime: options.startTime
},
contextmenuUI: {
content: [
{
label: 'Copy the video URL',
listener: function () {
copyToClipboard(buildVideoLink())
}
},
{
label: 'Copy the video URL at the current time',
listener: function () {
const player = this
copyToClipboard(buildVideoLink(player.currentTime()))
}
},
{
label: 'Copy embed code',
listener: () => {
copyToClipboard(buildVideoEmbed(options.videoEmbedUrl))
}
}
]
}
},
controlBar: {

View File

@ -268,6 +268,10 @@ class PeerTubePlugin extends Plugin {
this.trigger('autoResolutionUpdate')
}
getCurrentVideoFile () {
return this.currentVideoFile
}
private tryToPlay (done?: Function) {
if (!done) done = function () { /* empty */ }

View File

@ -64,14 +64,48 @@ function isMobile () {
return /iPhone|iPad|iPod|Android/i.test(navigator.userAgent)
}
function buildVideoLink (time?: number) {
let href = window.location.href.replace('/embed/', '/watch/')
if (time) {
const timeInt = Math.floor(time)
if (window.location.search) href += '&start=' + timeInt
else href += '?start=' + timeInt
}
return href
}
function buildVideoEmbed (embedUrl: string) {
return '<iframe width="560" height="315" ' +
'src="' + embedUrl + '" ' +
'frameborder="0" allowfullscreen>' +
'</iframe>'
}
function copyToClipboard (text: string) {
const el = document.createElement('textarea')
el.value = text
el.setAttribute('readonly', '')
el.style.position = 'absolute'
el.style.left = '-9999px'
document.body.appendChild(el)
el.select()
document.execCommand('copy')
document.body.removeChild(el)
}
export {
toTitleCase,
buildVideoLink,
getStoredVolume,
saveVolumeInStore,
saveAverageBandwidth,
getAverageBandwidth,
saveMuteInStore,
buildVideoEmbed,
getStoredMute,
copyToClipboard,
isMobile,
bytes
}

View File

@ -50,7 +50,7 @@ function renderMedia (file, elem: HTMLVideoElement, opts: RenderMediaOptions, ca
return fallbackToMediaSource()
})
preparedElem.addEventListener('canplay', onLoadStart)
preparedElem.addEventListener('loadstart', onLoadStart)
return videostream(file, preparedElem)
}
@ -66,7 +66,7 @@ function renderMedia (file, elem: HTMLVideoElement, opts: RenderMediaOptions, ca
return callback(err)
})
preparedElem.addEventListener('canplay', onLoadStart)
preparedElem.addEventListener('loadstart', onLoadStart)
const wrapper = new MediaElementWrapper(preparedElem)
const writable = wrapper.createWriteStream(codecs)
@ -95,7 +95,7 @@ function renderMedia (file, elem: HTMLVideoElement, opts: RenderMediaOptions, ca
}
function onLoadStart () {
preparedElem.removeEventListener('canplay', onLoadStart)
preparedElem.removeEventListener('loadstart', onLoadStart)
if (opts.autoplay) preparedElem.play()
callback(null, renderer)

View File

@ -21,6 +21,8 @@ $slider-bg-color: lighten($primary-background-color, 33%);
$setting-transition-duration: 0.15s;
$setting-transition-easing: ease-out;
$context-menu-width: 350px;
.video-js.vjs-peertube-skin {
font-size: $font-size;
color: $primary-foreground-color;
@ -787,4 +789,32 @@ $setting-transition-easing: ease-out;
}
}
}
}
/* Sass for videojs-contextmenu-ui */
.video-js .vjs-contextmenu-ui-menu {
position: absolute;
background-color: rgba(0, 0, 0, 0.5);
padding: 5px 0;
width: $context-menu-width;
.vjs-menu-content {
opacity: $primary-foreground-opacity;
color: $primary-foreground-color;
font-size: $font-size !important;
font-weight: $font-semibold;
}
.vjs-menu-item {
cursor: pointer;
font-size: 1em;
padding: 8px 16px;
text-align: left;
text-transform: none;
&:hover {
background-color: rgba(255, 255, 255, 0.2);
}
}
}

View File

@ -91,6 +91,7 @@ loadVideoInfo(videoId)
const videojsOptions = getVideojsOptions({
autoplay,
inactivityTimeout: 1500,
videoEmbedUrl: window.location.origin + videoInfo.embedPath,
videoViewUrl: getVideoUrl(videoId) + '/views',
playerElement: videoElement,
videoFiles: videoInfo.files,

View File

@ -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:
videojs-vtt.js "0.14.1"
xhr "2.4.0"
videojs-contextmenu-ui@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/videojs-contextmenu-ui/-/videojs-contextmenu-ui-4.0.0.tgz#e7ffceacac95c5d2bc7f80db6f75675404de542a"
dependencies:
global "^4.3.2"
video.js "^5.19.2"
videojs-contextmenu "^2.0.0"
videojs-contextmenu@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/videojs-contextmenu/-/videojs-contextmenu-2.0.0.tgz#7213c8c420ecd2904d26f19c21085f7ebf496e9e"
dependencies:
global "^4.3.2"
video.js "^5.19.2"
videojs-dock@^2.0.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/videojs-dock/-/videojs-dock-2.1.2.tgz#621c27c6f7dd131c541535300ac545377e515a0e"

View File

@ -65,7 +65,7 @@
"lint-staged": {
"*.{css,md}": "precise-commits",
"*.scss": [
"sass-lint-auto-fix -c .sass-lint.yml --verbose",
"sass-lint -c .sass-lint.yml",
"git add"
]
},
@ -156,7 +156,7 @@
"chai-json-schema": "^1.5.0",
"chai-xml": "^0.3.2",
"husky": "^1.0.0-rc.4",
"libxmljs": "^0.18.9-pre0",
"libxmljs": "0.18.8",
"lint-staged": "^7.1.0",
"maildev": "^1.0.0-rc3",
"mocha": "^5.0.0",
@ -165,7 +165,6 @@
"prettier": "1.13.2",
"prompt": "^1.0.0",
"sass-lint": "^1.12.1",
"sass-lint-auto-fix": "^0.10.0",
"source-map-support": "^0.5.0",
"spectacle-docs": "^1.0.2",
"supertest": "^3.0.0",

117
yarn.lock
View File

@ -398,7 +398,7 @@ ajv-keywords@^1.0.0:
version "1.5.1"
resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-1.5.1.tgz#314dd0a4b3368fad3dfcdc54ede6171b886daf3c"
ajv@^4.7.0, ajv@^4.9.1:
ajv@^4.7.0:
version "4.11.8"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.11.8.tgz#82ffb02b29e662ae53bdc20af15947706739c536"
dependencies:
@ -2849,15 +2849,7 @@ fsevents@^1.1.2:
nan "^2.9.2"
node-pre-gyp "^0.10.0"
fstream-ignore@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/fstream-ignore/-/fstream-ignore-1.0.5.tgz#9c31dae34767018fe1d249b24dada67d092da105"
dependencies:
fstream "^1.0.0"
inherits "2"
minimatch "^3.0.0"
fstream@^1.0.0, fstream@^1.0.10, fstream@^1.0.2:
fstream@^1.0.0, fstream@^1.0.2:
version "1.0.11"
resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.11.tgz#5c1fb1f117477114f0632a0eb4b71b3cb0fd3171"
dependencies:
@ -3308,10 +3300,6 @@ gzip-size@^3.0.0:
optionalDependencies:
uglify-js "^2.6"
har-schema@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-1.0.5.tgz#d263135f43307c02c602afc8fe95970c0151369e"
har-schema@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92"
@ -3325,13 +3313,6 @@ har-validator@~2.0.6:
is-my-json-valid "^2.12.4"
pinkie-promise "^2.0.0"
har-validator@~4.2.1:
version "4.2.1"
resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-4.2.1.tgz#33481d0f1bbff600dd203d75812a6a5fba002e2a"
dependencies:
ajv "^4.9.1"
har-schema "^1.0.5"
har-validator@~5.0.3:
version "5.0.3"
resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.0.3.tgz#ba402c266194f15956ef15e0fcf242993f6a7dfd"
@ -3419,7 +3400,7 @@ hashish@~0.0.4:
dependencies:
traverse ">=0.2.4"
hawk@3.1.3, hawk@~3.1.3:
hawk@~3.1.3:
version "3.1.3"
resolved "https://registry.yarnpkg.com/hawk/-/hawk-3.1.3.tgz#078444bd7c1640b0fe540d2c9b73d59678e8e1c4"
dependencies:
@ -4451,13 +4432,13 @@ levn@^0.3.0, levn@~0.3.0:
prelude-ls "~1.1.2"
type-check "~0.3.2"
libxmljs@^0.18.9-pre0:
version "0.18.9-pre0"
resolved "https://registry.yarnpkg.com/libxmljs/-/libxmljs-0.18.9-pre0.tgz#2e607aae228d856777bab1205d54e0dcefe825ba"
libxmljs@0.18.8:
version "0.18.8"
resolved "https://registry.yarnpkg.com/libxmljs/-/libxmljs-0.18.8.tgz#b0a07512a01290b6240600d6c2bc33a3c70976d6"
dependencies:
bindings "~1.3.0"
bindings "^1.3.0"
nan "~2.10.0"
node-pre-gyp "~0.6.37"
node-pre-gyp "^0.9.1"
lint-staged@^7.1.0:
version "7.1.2"
@ -5013,7 +4994,7 @@ minimatch@0.3:
lru-cache "2"
sigmund "~1.0.0"
"minimatch@2 || 3", minimatch@3.0.4, minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.4, minimatch@~3.0.2:
"minimatch@2 || 3", minimatch@3.0.4, minimatch@^3.0.2, minimatch@^3.0.4, minimatch@~3.0.2:
version "3.0.4"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
dependencies:
@ -5283,7 +5264,7 @@ node-gyp@^3.3.1:
tar "^2.0.0"
which "1"
node-pre-gyp@0.9.1:
node-pre-gyp@0.9.1, node-pre-gyp@^0.9.1:
version "0.9.1"
resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.9.1.tgz#f11c07516dd92f87199dbc7e1838eab7cd56c9e0"
dependencies:
@ -5313,22 +5294,6 @@ node-pre-gyp@^0.10.0:
semver "^5.3.0"
tar "^4"
node-pre-gyp@~0.6.37:
version "0.6.39"
resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.6.39.tgz#c00e96860b23c0e1420ac7befc5044e1d78d8649"
dependencies:
detect-libc "^1.0.2"
hawk "3.1.3"
mkdirp "^0.5.1"
nopt "^4.0.1"
npmlog "^4.0.2"
rc "^1.1.7"
request "2.81.0"
rimraf "^2.6.1"
semver "^5.3.0"
tar "^2.2.1"
tar-pack "^3.4.0"
node-redis-scripty@0.0.5:
version "0.0.5"
resolved "https://registry.yarnpkg.com/node-redis-scripty/-/node-redis-scripty-0.0.5.tgz#4bf2d365ab6dab202cc08b7ac63f8f55aadc9625"
@ -5866,10 +5831,6 @@ pem@^1.12.3:
safe-buffer "^5.1.1"
which "^1.2.4"
performance-now@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-0.2.0.tgz#33ef30c5c77d4ea21c5a53869d91b56d8f2555e5"
performance-now@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
@ -6289,10 +6250,6 @@ qs@~6.3.0:
version "6.3.2"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.3.2.tgz#e75bd5f6e268122a2a0e0bda630b2550c166502c"
qs@~6.4.0:
version "6.4.0"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233"
random-access-file@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/random-access-file/-/random-access-file-2.0.1.tgz#dc22de79270e9a84cb36a2419b759725930dcaeb"
@ -6604,33 +6561,6 @@ request@2, request@^2.81.0, request@^2.83.0:
tunnel-agent "^0.6.0"
uuid "^3.1.0"
request@2.81.0:
version "2.81.0"
resolved "https://registry.yarnpkg.com/request/-/request-2.81.0.tgz#c6928946a0e06c5f8d6f8a9333469ffda46298a0"
dependencies:
aws-sign2 "~0.6.0"
aws4 "^1.2.1"
caseless "~0.12.0"
combined-stream "~1.0.5"
extend "~3.0.0"
forever-agent "~0.6.1"
form-data "~2.1.1"
har-validator "~4.2.1"
hawk "~3.1.3"
http-signature "~1.1.0"
is-typedarray "~1.0.0"
isstream "~0.1.2"
json-stringify-safe "~5.0.1"
mime-types "~2.1.7"
oauth-sign "~0.8.1"
performance-now "^0.2.0"
qs "~6.4.0"
safe-buffer "^5.0.1"
stringstream "~0.0.4"
tough-cookie "~2.3.0"
tunnel-agent "^0.6.0"
uuid "^3.0.0"
request@~2.79.0:
version "2.79.0"
resolved "https://registry.yarnpkg.com/request/-/request-2.79.0.tgz#4dfe5bf6be8b8cdc37fcf93e04b65577722710de"
@ -6734,7 +6664,7 @@ right-align@^0.1.1:
dependencies:
align-text "^0.1.1"
rimraf@2, rimraf@2.x.x, rimraf@^2.2.1, rimraf@^2.2.8, rimraf@^2.4.2, rimraf@^2.5.1, rimraf@^2.5.4, rimraf@^2.6.1:
rimraf@2, rimraf@2.x.x, rimraf@^2.2.1, rimraf@^2.2.8, rimraf@^2.4.2, rimraf@^2.5.4, rimraf@^2.6.1:
version "2.6.2"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36"
dependencies:
@ -6811,9 +6741,9 @@ sass-graph@^2.2.4:
scss-tokenizer "^0.2.3"
yargs "^7.0.0"
sass-lint-auto-fix@^0.10.0:
version "0.10.0"
resolved "https://registry.yarnpkg.com/sass-lint-auto-fix/-/sass-lint-auto-fix-0.10.0.tgz#89fd3cfd29fae30ddb7bbefa488053553ef0f10e"
sass-lint-auto-fix@^0.9.0:
version "0.9.2"
resolved "https://registry.yarnpkg.com/sass-lint-auto-fix/-/sass-lint-auto-fix-0.9.2.tgz#b8b6eb95644f7919dfea33d04c1fc19ae8f07a11"
dependencies:
chalk "^2.3.2"
commander "^2.15.1"
@ -7693,19 +7623,6 @@ tar-fs@^1.13.0:
pump "^1.0.0"
tar-stream "^1.1.2"
tar-pack@^3.4.0:
version "3.4.1"
resolved "https://registry.yarnpkg.com/tar-pack/-/tar-pack-3.4.1.tgz#e1dbc03a9b9d3ba07e896ad027317eb679a10a1f"
dependencies:
debug "^2.2.0"
fstream "^1.0.10"
fstream-ignore "^1.0.5"
once "^1.3.3"
readable-stream "^2.1.4"
rimraf "^2.5.1"
tar "^2.2.1"
uid-number "^0.0.6"
tar-stream@^1.1.2:
version "1.6.1"
resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.6.1.tgz#f84ef1696269d6223ca48f6e1eeede3f7e81f395"
@ -7718,7 +7635,7 @@ tar-stream@^1.1.2:
to-buffer "^1.1.0"
xtend "^4.0.0"
tar@^2.0.0, tar@^2.2.1:
tar@^2.0.0:
version "2.2.1"
resolved "https://registry.yarnpkg.com/tar/-/tar-2.2.1.tgz#8e4d2a256c0e2185c6b18ad694aec968b83cb1d1"
dependencies:
@ -8054,10 +7971,6 @@ uglify-to-browserify@~1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7"
uid-number@^0.0.6:
version "0.0.6"
resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81"
uint64be@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/uint64be/-/uint64be-1.0.1.tgz#1f7154202f2a1b8af353871dda651bf34ce93e95"