aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--client/src/app/+admin/moderation/video-block-list/video-block-list.component.ts4
-rw-r--r--client/src/app/+videos/+video-watch/comment/video-comment.component.ts2
-rw-r--r--client/src/app/+videos/+video-watch/modal/video-share.component.html52
-rw-r--r--client/src/app/+videos/+video-watch/modal/video-share.component.ts41
-rw-r--r--client/src/app/+videos/+video-watch/video-watch.component.ts2
-rw-r--r--client/src/app/shared/shared-abuse-list/abuse-list-table.component.ts4
-rw-r--r--client/src/app/shared/shared-moderation/report-modals/video-report.component.ts4
-rw-r--r--client/src/app/shared/shared-video-playlist/video-playlist.model.ts8
-rw-r--r--client/src/assets/player/peertube-player-manager.ts4
-rw-r--r--client/src/assets/player/utils.ts59
-rw-r--r--server/models/video/video-playlist.ts1
-rw-r--r--server/tests/api/videos/video-playlists.ts3
-rw-r--r--shared/models/videos/playlist/video-playlist.model.ts2
13 files changed, 143 insertions, 43 deletions
diff --git a/client/src/app/+admin/moderation/video-block-list/video-block-list.component.ts b/client/src/app/+admin/moderation/video-block-list/video-block-list.component.ts
index 3242bcf46..422d873c0 100644
--- a/client/src/app/+admin/moderation/video-block-list/video-block-list.component.ts
+++ b/client/src/app/+admin/moderation/video-block-list/video-block-list.component.ts
@@ -7,7 +7,7 @@ import { DropdownAction, Video, VideoService } from '@app/shared/shared-main'
7import { VideoBlockService } from '@app/shared/shared-moderation' 7import { VideoBlockService } from '@app/shared/shared-moderation'
8import { I18n } from '@ngx-translate/i18n-polyfill' 8import { I18n } from '@ngx-translate/i18n-polyfill'
9import { VideoBlacklist, VideoBlacklistType } from '@shared/models' 9import { VideoBlacklist, VideoBlacklistType } from '@shared/models'
10import { buildVideoEmbed, buildVideoLink } from 'src/assets/player/utils' 10import { buildVideoOrPlaylistEmbed, buildVideoLink } from 'src/assets/player/utils'
11import { environment } from 'src/environments/environment' 11import { environment } from 'src/environments/environment'
12import { DomSanitizer } from '@angular/platform-browser' 12import { DomSanitizer } from '@angular/platform-browser'
13 13
@@ -176,7 +176,7 @@ export class VideoBlockListComponent extends RestTable implements OnInit, AfterV
176 } 176 }
177 177
178 getVideoEmbed (entry: VideoBlacklist) { 178 getVideoEmbed (entry: VideoBlacklist) {
179 return buildVideoEmbed( 179 return buildVideoOrPlaylistEmbed(
180 buildVideoLink({ 180 buildVideoLink({
181 baseUrl: `${environment.embedUrl}/videos/embed/${entry.video.uuid}`, 181 baseUrl: `${environment.embedUrl}/videos/embed/${entry.video.uuid}`,
182 title: false, 182 title: false,
diff --git a/client/src/app/+videos/+video-watch/comment/video-comment.component.ts b/client/src/app/+videos/+video-watch/comment/video-comment.component.ts
index 6744a0954..36ec6e9f9 100644
--- a/client/src/app/+videos/+video-watch/comment/video-comment.component.ts
+++ b/client/src/app/+videos/+video-watch/comment/video-comment.component.ts
@@ -135,7 +135,7 @@ export class VideoCommentComponent implements OnInit, OnChanges {
135 this.comment.account = null 135 this.comment.account = null
136 } 136 }
137 137
138 if (this.isUserLoggedIn() && this.authService.getUser().account.id !== this.comment.account.id) { 138 if (this.isUserLoggedIn() && this.comment.isDeleted === false && this.authService.getUser().account.id !== this.comment.account.id) {
139 this.prependModerationActions = [ 139 this.prependModerationActions = [
140 { 140 {
141 label: this.i18n('Report comment'), 141 label: this.i18n('Report comment'),
diff --git a/client/src/app/+videos/+video-watch/modal/video-share.component.html b/client/src/app/+videos/+video-watch/modal/video-share.component.html
index 71ae6544f..946e8d8ca 100644
--- a/client/src/app/+videos/+video-watch/modal/video-share.component.html
+++ b/client/src/app/+videos/+video-watch/modal/video-share.component.html
@@ -6,18 +6,56 @@
6 6
7 7
8 <div class="modal-body"> 8 <div class="modal-body">
9
9 <div class="playlist" *ngIf="hasPlaylist()"> 10 <div class="playlist" *ngIf="hasPlaylist()">
10 <div class="title-page title-page-single" i18n>Share the playlist</div> 11 <div class="title-page title-page-single" i18n>Share the playlist</div>
11 12
12 <my-input-readonly-copy [value]="getPlaylistUrl()"></my-input-readonly-copy> 13 <div ngbNav #nav="ngbNav" class="nav-tabs" [(activeId)]="activePlaylistId">
14
15 <ng-container ngbNavItem="url">
16 <a ngbNavLink i18n>URL</a>
17
18 <ng-template ngbNavContent>
19 <div class="nav-content">
20
21 <my-input-readonly-copy [value]="getPlaylistUrl()"></my-input-readonly-copy>
22 </div>
23 </ng-template>
24 </ng-container>
25
26 <ng-container ngbNavItem="qrcode">
27 <a ngbNavLink i18n>QR-Code</a>
28
29 <ng-template ngbNavContent>
30 <div class="nav-content">
31 <qrcode [qrdata]="getPlaylistUrl()" [size]="256" level="Q"></qrcode>
32 </div>
33 </ng-template>
34 </ng-container>
35
36 <ng-container ngbNavItem="embed">
37 <a ngbNavLink i18n>Embed</a>
38
39 <ng-template ngbNavContent>
40 <div class="nav-content">
41 <my-input-readonly-copy [value]="getPlaylistIframeCode()"></my-input-readonly-copy>
42
43 <div i18n *ngIf="notSecure()" class="alert alert-warning">
44 The url is not secured (no HTTPS), so the embed video won't work on HTTPS websites (web browsers block non secured HTTP requests on HTTPS websites).
45 </div>
46 </div>
47 </ng-template>
48 </ng-container>
49
50 </div>
51
52 <div [ngbNavOutlet]="nav"></div>
13 53
14 <div class="filters"> 54 <div class="filters">
15 55
16 <div class="form-group"> 56 <div class="form-group">
17 <my-peertube-checkbox 57 <my-peertube-checkbox inputName="includeVideoInPlaylist" [(ngModel)]="includeVideoInPlaylist" i18n-labelText
18 inputName="includeVideoInPlaylist" [(ngModel)]="includeVideoInPlaylist" 58 labelText="Share the playlist at this video position"></my-peertube-checkbox>
19 i18n-labelText labelText="Share the playlist at this video position"
20 ></my-peertube-checkbox>
21 </div> 59 </div>
22 60
23 </div> 61 </div>
@@ -27,7 +65,7 @@
27 <div class="video"> 65 <div class="video">
28 <div class="title-page title-page-single" *ngIf="hasPlaylist()" i18n>Share the video</div> 66 <div class="title-page title-page-single" *ngIf="hasPlaylist()" i18n>Share the video</div>
29 67
30 <div ngbNav #nav="ngbNav" class="nav-tabs" [(activeId)]="activeId"> 68 <div ngbNav #nav="ngbNav" class="nav-tabs" [(activeId)]="activeVideoId">
31 69
32 <ng-container ngbNavItem="url"> 70 <ng-container ngbNavItem="url">
33 <a ngbNavLink i18n>URL</a> 71 <a ngbNavLink i18n>URL</a>
@@ -137,7 +175,7 @@
137 </div> 175 </div>
138 </div> 176 </div>
139 177
140 <ng-container *ngIf="isInEmbedTab()"> 178 <ng-container *ngIf="isVideoInEmbedTab()">
141 <div class="form-group"> 179 <div class="form-group">
142 <my-peertube-checkbox 180 <my-peertube-checkbox
143 inputName="title" [(ngModel)]="customizations.title" 181 inputName="title" [(ngModel)]="customizations.title"
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 23c562273..d9171fe0e 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
@@ -1,5 +1,5 @@
1import { Component, ElementRef, Input, ViewChild } from '@angular/core' 1import { Component, ElementRef, Input, ViewChild } from '@angular/core'
2import { buildVideoEmbed, buildVideoLink } from '../../../../assets/player/utils' 2import { buildVideoOrPlaylistEmbed, buildVideoLink, buildPlaylistLink } from '../../../../assets/player/utils'
3import { NgbModal } from '@ng-bootstrap/ng-bootstrap' 3import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
4import { VideoCaption } from '@shared/models' 4import { VideoCaption } from '@shared/models'
5import { VideoDetails } from '@app/shared/shared-main' 5import { VideoDetails } from '@app/shared/shared-main'
@@ -24,6 +24,8 @@ type Customizations = {
24 peertubeLink: boolean 24 peertubeLink: boolean
25} 25}
26 26
27type TabId = 'url' | 'qrcode' | 'embed'
28
27@Component({ 29@Component({
28 selector: 'my-video-share', 30 selector: 'my-video-share',
29 templateUrl: './video-share.component.html', 31 templateUrl: './video-share.component.html',
@@ -36,14 +38,18 @@ export class VideoShareComponent {
36 @Input() videoCaptions: VideoCaption[] = [] 38 @Input() videoCaptions: VideoCaption[] = []
37 @Input() playlist: VideoPlaylist = null 39 @Input() playlist: VideoPlaylist = null
38 40
39 activeId: 'url' | 'qrcode' | 'embed' = 'url' 41 activeVideoId: TabId = 'url'
42 activePlaylistId: TabId = 'url'
43
40 customizations: Customizations 44 customizations: Customizations
41 isAdvancedCustomizationCollapsed = true 45 isAdvancedCustomizationCollapsed = true
42 includeVideoInPlaylist = false 46 includeVideoInPlaylist = false
43 47
48 private playlistPosition: number = null
49
44 constructor (private modalService: NgbModal) { } 50 constructor (private modalService: NgbModal) { }
45 51
46 show (currentVideoTimestamp?: number) { 52 show (currentVideoTimestamp?: number, currentPlaylistPosition?: number) {
47 let subtitle: string 53 let subtitle: string
48 if (this.videoCaptions.length !== 0) { 54 if (this.videoCaptions.length !== 0) {
49 subtitle = this.videoCaptions[0].language.id 55 subtitle = this.videoCaptions[0].language.id
@@ -70,19 +76,28 @@ export class VideoShareComponent {
70 peertubeLink: true 76 peertubeLink: true
71 } 77 }
72 78
79 this.playlistPosition = currentPlaylistPosition
80
73 this.modalService.open(this.modal, { centered: true }) 81 this.modalService.open(this.modal, { centered: true })
74 } 82 }
75 83
76 getVideoIframeCode () { 84 getVideoIframeCode () {
77 const options = this.getOptions(this.video.embedUrl) 85 const options = this.getVideoOptions(this.video.embedUrl)
78 86
79 const embedUrl = buildVideoLink(options) 87 const embedUrl = buildVideoLink(options)
80 return buildVideoEmbed(embedUrl) 88 return buildVideoOrPlaylistEmbed(embedUrl)
89 }
90
91 getPlaylistIframeCode () {
92 const options = this.getPlaylistOptions(this.playlist.embedUrl)
93
94 const embedUrl = buildPlaylistLink(options)
95 return buildVideoOrPlaylistEmbed(embedUrl)
81 } 96 }
82 97
83 getVideoUrl () { 98 getVideoUrl () {
84 const baseUrl = window.location.origin + '/videos/watch/' + this.video.uuid 99 const baseUrl = window.location.origin + '/videos/watch/' + this.video.uuid
85 const options = this.getOptions(baseUrl) 100 const options = this.getVideoOptions(baseUrl)
86 101
87 return buildVideoLink(options) 102 return buildVideoLink(options)
88 } 103 }
@@ -99,15 +114,23 @@ export class VideoShareComponent {
99 return window.location.protocol === 'http:' 114 return window.location.protocol === 'http:'
100 } 115 }
101 116
102 isInEmbedTab () { 117 isVideoInEmbedTab () {
103 return this.activeId === 'embed' 118 return this.activeVideoId === 'embed'
104 } 119 }
105 120
106 hasPlaylist () { 121 hasPlaylist () {
107 return !!this.playlist 122 return !!this.playlist
108 } 123 }
109 124
110 private getOptions (baseUrl?: string) { 125 private getPlaylistOptions (baseUrl?: string) {
126 return {
127 baseUrl,
128
129 playlistPosition: this.playlistPosition || undefined
130 }
131 }
132
133 private getVideoOptions (baseUrl?: string) {
111 return { 134 return {
112 baseUrl, 135 baseUrl,
113 136
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 dfe73d14d..d8136ab4f 100644
--- a/client/src/app/+videos/+video-watch/video-watch.component.ts
+++ b/client/src/app/+videos/+video-watch/video-watch.component.ts
@@ -244,7 +244,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
244 showShareModal () { 244 showShareModal () {
245 this.pausePlayer() 245 this.pausePlayer()
246 246
247 this.videoShareModal.show(this.currentTime) 247 this.videoShareModal.show(this.currentTime, this.videoWatchPlaylist.currentPlaylistPosition)
248 } 248 }
249 249
250 isUserLoggedIn () { 250 isUserLoggedIn () {
diff --git a/client/src/app/shared/shared-abuse-list/abuse-list-table.component.ts b/client/src/app/shared/shared-abuse-list/abuse-list-table.component.ts
index 21d2ea47d..c7dc5f4d2 100644
--- a/client/src/app/shared/shared-abuse-list/abuse-list-table.component.ts
+++ b/client/src/app/shared/shared-abuse-list/abuse-list-table.component.ts
@@ -1,7 +1,7 @@
1import * as debug from 'debug' 1import * as debug from 'debug'
2import truncate from 'lodash-es/truncate' 2import truncate from 'lodash-es/truncate'
3import { SortMeta } from 'primeng/api' 3import { SortMeta } from 'primeng/api'
4import { buildVideoEmbed, buildVideoLink } from 'src/assets/player/utils' 4import { buildVideoOrPlaylistEmbed, buildVideoLink } from 'src/assets/player/utils'
5import { environment } from 'src/environments/environment' 5import { environment } from 'src/environments/environment'
6import { AfterViewInit, Component, OnInit, ViewChild, Input } from '@angular/core' 6import { AfterViewInit, Component, OnInit, ViewChild, Input } from '@angular/core'
7import { DomSanitizer } from '@angular/platform-browser' 7import { DomSanitizer } from '@angular/platform-browser'
@@ -141,7 +141,7 @@ export class AbuseListTableComponent extends RestTable implements OnInit, AfterV
141 } 141 }
142 142
143 getVideoEmbed (abuse: AdminAbuse) { 143 getVideoEmbed (abuse: AdminAbuse) {
144 return buildVideoEmbed( 144 return buildVideoOrPlaylistEmbed(
145 buildVideoLink({ 145 buildVideoLink({
146 baseUrl: `${environment.embedUrl}/videos/embed/${abuse.video.uuid}`, 146 baseUrl: `${environment.embedUrl}/videos/embed/${abuse.video.uuid}`,
147 title: false, 147 title: false,
diff --git a/client/src/app/shared/shared-moderation/report-modals/video-report.component.ts b/client/src/app/shared/shared-moderation/report-modals/video-report.component.ts
index 09ab98dfe..794dd54bb 100644
--- a/client/src/app/shared/shared-moderation/report-modals/video-report.component.ts
+++ b/client/src/app/shared/shared-moderation/report-modals/video-report.component.ts
@@ -1,5 +1,5 @@
1import { mapValues, pickBy } from 'lodash-es' 1import { mapValues, pickBy } from 'lodash-es'
2import { buildVideoEmbed, buildVideoLink } from 'src/assets/player/utils' 2import { buildVideoOrPlaylistEmbed, buildVideoLink } from 'src/assets/player/utils'
3import { Component, Input, OnInit, ViewChild } from '@angular/core' 3import { Component, Input, OnInit, ViewChild } from '@angular/core'
4import { DomSanitizer, SafeHtml } from '@angular/platform-browser' 4import { DomSanitizer, SafeHtml } from '@angular/platform-browser'
5import { Notifier } from '@app/core' 5import { Notifier } from '@app/core'
@@ -58,7 +58,7 @@ export class VideoReportComponent extends FormReactive implements OnInit {
58 58
59 getVideoEmbed () { 59 getVideoEmbed () {
60 return this.sanitizer.bypassSecurityTrustHtml( 60 return this.sanitizer.bypassSecurityTrustHtml(
61 buildVideoEmbed( 61 buildVideoOrPlaylistEmbed(
62 buildVideoLink({ 62 buildVideoLink({
63 baseUrl: this.video.embedUrl, 63 baseUrl: this.video.embedUrl,
64 title: false, 64 title: false,
diff --git a/client/src/app/shared/shared-video-playlist/video-playlist.model.ts b/client/src/app/shared/shared-video-playlist/video-playlist.model.ts
index 7de379cdf..3db3b7a2e 100644
--- a/client/src/app/shared/shared-video-playlist/video-playlist.model.ts
+++ b/client/src/app/shared/shared-video-playlist/video-playlist.model.ts
@@ -1,4 +1,4 @@
1import { getAbsoluteAPIUrl } from '@app/helpers' 1import { getAbsoluteAPIUrl, getAbsoluteEmbedUrl } from '@app/helpers'
2import { Actor } from '@app/shared/shared-main' 2import { Actor } from '@app/shared/shared-main'
3import { peertubeTranslate } from '@shared/core-utils/i18n' 3import { peertubeTranslate } from '@shared/core-utils/i18n'
4import { 4import {
@@ -33,6 +33,9 @@ export class VideoPlaylist implements ServerVideoPlaylist {
33 33
34 thumbnailUrl: string 34 thumbnailUrl: string
35 35
36 embedPath: string
37 embedUrl: string
38
36 ownerBy: string 39 ownerBy: string
37 ownerAvatarUrl: string 40 ownerAvatarUrl: string
38 41
@@ -63,6 +66,9 @@ export class VideoPlaylist implements ServerVideoPlaylist {
63 this.thumbnailUrl = window.location.origin + '/client/assets/images/default-playlist.jpg' 66 this.thumbnailUrl = window.location.origin + '/client/assets/images/default-playlist.jpg'
64 } 67 }
65 68
69 this.embedPath = hash.embedPath
70 this.embedUrl = getAbsoluteEmbedUrl() + hash.embedPath
71
66 this.videosLength = hash.videosLength 72 this.videosLength = hash.videosLength
67 73
68 this.type = hash.type 74 this.type = hash.type
diff --git a/client/src/assets/player/peertube-player-manager.ts b/client/src/assets/player/peertube-player-manager.ts
index c71b43415..15b2f420b 100644
--- a/client/src/assets/player/peertube-player-manager.ts
+++ b/client/src/assets/player/peertube-player-manager.ts
@@ -35,7 +35,7 @@ import {
35 VideoJSPluginOptions 35 VideoJSPluginOptions
36} from './peertube-videojs-typings' 36} from './peertube-videojs-typings'
37import { TranslationsManager } from './translations-manager' 37import { TranslationsManager } from './translations-manager'
38import { buildVideoEmbed, buildVideoLink, copyToClipboard, getRtcConfig, isIOS, isSafari } from './utils' 38import { buildVideoOrPlaylistEmbed, buildVideoLink, copyToClipboard, getRtcConfig, isIOS, isSafari } from './utils'
39 39
40// Change 'Playback Rate' to 'Speed' (smaller for our settings menu) 40// Change 'Playback Rate' to 'Speed' (smaller for our settings menu)
41(videojs.getComponent('PlaybackRateMenuButton') as any).prototype.controlText_ = 'Speed' 41(videojs.getComponent('PlaybackRateMenuButton') as any).prototype.controlText_ = 'Speed'
@@ -492,7 +492,7 @@ export class PeertubePlayerManager {
492 { 492 {
493 label: player.localize('Copy embed code'), 493 label: player.localize('Copy embed code'),
494 listener: () => { 494 listener: () => {
495 copyToClipboard(buildVideoEmbed(videoEmbedUrl)) 495 copyToClipboard(buildVideoOrPlaylistEmbed(videoEmbedUrl))
496 } 496 }
497 } 497 }
498 ] 498 ]
diff --git a/client/src/assets/player/utils.ts b/client/src/assets/player/utils.ts
index 115fdfa49..ce7a7fe6c 100644
--- a/client/src/assets/player/utils.ts
+++ b/client/src/assets/player/utils.ts
@@ -43,20 +43,20 @@ function isMobile () {
43} 43}
44 44
45function buildVideoLink (options: { 45function buildVideoLink (options: {
46 baseUrl?: string, 46 baseUrl?: string
47 47
48 startTime?: number, 48 startTime?: number
49 stopTime?: number, 49 stopTime?: number
50 50
51 subtitle?: string, 51 subtitle?: string
52 52
53 loop?: boolean, 53 loop?: boolean
54 autoplay?: boolean, 54 autoplay?: boolean
55 muted?: boolean, 55 muted?: boolean
56 56
57 // Embed options 57 // Embed options
58 title?: boolean, 58 title?: boolean
59 warningTitle?: boolean, 59 warningTitle?: boolean
60 controls?: boolean 60 controls?: boolean
61 peertubeLink?: boolean 61 peertubeLink?: boolean
62} = {}) { 62} = {}) {
@@ -66,10 +66,7 @@ function buildVideoLink (options: {
66 ? baseUrl 66 ? baseUrl
67 : window.location.origin + window.location.pathname.replace('/embed/', '/watch/') 67 : window.location.origin + window.location.pathname.replace('/embed/', '/watch/')
68 68
69 const params = new URLSearchParams(window.location.search) 69 const params = generateParams(window.location.search)
70 // Remove these unused parameters when we are on a playlist page
71 params.delete('videoId')
72 params.delete('resume')
73 70
74 if (options.startTime) { 71 if (options.startTime) {
75 const startTimeInt = Math.floor(options.startTime) 72 const startTimeInt = Math.floor(options.startTime)
@@ -91,6 +88,28 @@ function buildVideoLink (options: {
91 if (options.controls === false) params.set('controls', '0') 88 if (options.controls === false) params.set('controls', '0')
92 if (options.peertubeLink === false) params.set('peertubeLink', '0') 89 if (options.peertubeLink === false) params.set('peertubeLink', '0')
93 90
91 return buildUrl(url, params)
92}
93
94function buildPlaylistLink (options: {
95 baseUrl?: string
96
97 playlistPosition: number
98}) {
99 const { baseUrl } = options
100
101 const url = baseUrl
102 ? baseUrl
103 : window.location.origin + window.location.pathname.replace('/video-playlists/embed/', '/videos/watch/playlist/')
104
105 const params = generateParams(window.location.search)
106
107 if (options.playlistPosition) params.set('playlistPosition', '' + options.playlistPosition)
108
109 return buildUrl(url, params)
110}
111
112function buildUrl (url: string, params: URLSearchParams) {
94 let hasParams = false 113 let hasParams = false
95 params.forEach(() => hasParams = true) 114 params.forEach(() => hasParams = true)
96 115
@@ -99,6 +118,15 @@ function buildVideoLink (options: {
99 return url 118 return url
100} 119}
101 120
121function generateParams (url: string) {
122 const params = new URLSearchParams(window.location.search)
123 // Unused parameters in embed
124 params.delete('videoId')
125 params.delete('resume')
126
127 return params
128}
129
102function timeToInt (time: number | string) { 130function timeToInt (time: number | string) {
103 if (!time) return 0 131 if (!time) return 0
104 if (typeof time === 'number') return time 132 if (typeof time === 'number') return time
@@ -140,7 +168,7 @@ function secondsToTime (seconds: number, full = false, symbol?: string) {
140 return time 168 return time
141} 169}
142 170
143function buildVideoEmbed (embedUrl: string) { 171function buildVideoOrPlaylistEmbed (embedUrl: string) {
144 return '<iframe width="560" height="315" ' + 172 return '<iframe width="560" height="315" ' +
145 'sandbox="allow-same-origin allow-scripts allow-popups" ' + 173 'sandbox="allow-same-origin allow-scripts allow-popups" ' +
146 'src="' + embedUrl + '" ' + 174 'src="' + embedUrl + '" ' +
@@ -203,8 +231,9 @@ export {
203 timeToInt, 231 timeToInt,
204 secondsToTime, 232 secondsToTime,
205 isWebRTCDisabled, 233 isWebRTCDisabled,
234 buildPlaylistLink,
206 buildVideoLink, 235 buildVideoLink,
207 buildVideoEmbed, 236 buildVideoOrPlaylistEmbed,
208 videoFileMaxByResolution, 237 videoFileMaxByResolution,
209 videoFileMinByResolution, 238 videoFileMinByResolution,
210 copyToClipboard, 239 copyToClipboard,
diff --git a/server/models/video/video-playlist.ts b/server/models/video/video-playlist.ts
index f935bf4f0..581179640 100644
--- a/server/models/video/video-playlist.ts
+++ b/server/models/video/video-playlist.ts
@@ -528,6 +528,7 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> {
528 }, 528 },
529 529
530 thumbnailPath: this.getThumbnailStaticPath(), 530 thumbnailPath: this.getThumbnailStaticPath(),
531 embedPath: this.getEmbedStaticPath(),
531 532
532 type: { 533 type: {
533 id: this.type, 534 id: this.type,
diff --git a/server/tests/api/videos/video-playlists.ts b/server/tests/api/videos/video-playlists.ts
index 2bb97d7a8..52b32998d 100644
--- a/server/tests/api/videos/video-playlists.ts
+++ b/server/tests/api/videos/video-playlists.ts
@@ -236,7 +236,7 @@ describe('Test video playlists', function () {
236 const playlistFromList = res.body.data[0] as VideoPlaylist 236 const playlistFromList = res.body.data[0] as VideoPlaylist
237 237
238 const res2 = await getVideoPlaylist(server.url, playlistFromList.uuid) 238 const res2 = await getVideoPlaylist(server.url, playlistFromList.uuid)
239 const playlistFromGet = res2.body 239 const playlistFromGet = res2.body as VideoPlaylist
240 240
241 for (const playlist of [ playlistFromGet, playlistFromList ]) { 241 for (const playlist of [ playlistFromGet, playlistFromList ]) {
242 expect(playlist.id).to.be.a('number') 242 expect(playlist.id).to.be.a('number')
@@ -250,6 +250,7 @@ describe('Test video playlists', function () {
250 expect(playlist.privacy.label).to.equal('Public') 250 expect(playlist.privacy.label).to.equal('Public')
251 expect(playlist.type.id).to.equal(VideoPlaylistType.REGULAR) 251 expect(playlist.type.id).to.equal(VideoPlaylistType.REGULAR)
252 expect(playlist.type.label).to.equal('Regular') 252 expect(playlist.type.label).to.equal('Regular')
253 expect(playlist.embedPath).to.equal('/video-playlists/embed/' + playlist.uuid)
253 254
254 expect(playlist.videosLength).to.equal(0) 255 expect(playlist.videosLength).to.equal(0)
255 256
diff --git a/shared/models/videos/playlist/video-playlist.model.ts b/shared/models/videos/playlist/video-playlist.model.ts
index c0941727a..f45d0ff88 100644
--- a/shared/models/videos/playlist/video-playlist.model.ts
+++ b/shared/models/videos/playlist/video-playlist.model.ts
@@ -18,6 +18,8 @@ export interface VideoPlaylist {
18 18
19 type: VideoConstant<VideoPlaylistType> 19 type: VideoConstant<VideoPlaylistType>
20 20
21 embedPath: string
22
21 createdAt: Date | string 23 createdAt: Date | string
22 updatedAt: Date | string 24 updatedAt: Date | string
23 25