diff options
author | Chocobozzz <me@florianbigard.com> | 2019-02-26 10:55:40 +0100 |
---|---|---|
committer | Chocobozzz <chocobozzz@cpy.re> | 2019-03-18 11:17:59 +0100 |
commit | 418d092afa81e2c8fe8ac6838fc4b5eb0af6a782 (patch) | |
tree | 5e9bc5604fd5d66a006cfebb7acdbdd5486e5d1e /shared | |
parent | b427febb4d5cebf03b815bca2c59af6e82491569 (diff) | |
download | PeerTube-418d092afa81e2c8fe8ac6838fc4b5eb0af6a782.tar.gz PeerTube-418d092afa81e2c8fe8ac6838fc4b5eb0af6a782.tar.zst PeerTube-418d092afa81e2c8fe8ac6838fc4b5eb0af6a782.zip |
Playlist server API
Diffstat (limited to 'shared')
19 files changed, 373 insertions, 61 deletions
diff --git a/shared/models/activitypub/activity.ts b/shared/models/activitypub/activity.ts index 89994f665..95801190d 100644 --- a/shared/models/activitypub/activity.ts +++ b/shared/models/activitypub/activity.ts | |||
@@ -6,6 +6,7 @@ import { VideoAbuseObject } from './objects/video-abuse-object' | |||
6 | import { VideoCommentObject } from './objects/video-comment-object' | 6 | import { VideoCommentObject } from './objects/video-comment-object' |
7 | import { ViewObject } from './objects/view-object' | 7 | import { ViewObject } from './objects/view-object' |
8 | import { APObject } from './objects/object.model' | 8 | import { APObject } from './objects/object.model' |
9 | import { PlaylistObject } from './objects/playlist-object' | ||
9 | 10 | ||
10 | export type Activity = ActivityCreate | ActivityUpdate | | 11 | export type Activity = ActivityCreate | ActivityUpdate | |
11 | ActivityDelete | ActivityFollow | ActivityAccept | ActivityAnnounce | | 12 | ActivityDelete | ActivityFollow | ActivityAccept | ActivityAnnounce | |
@@ -31,12 +32,12 @@ export interface BaseActivity { | |||
31 | 32 | ||
32 | export interface ActivityCreate extends BaseActivity { | 33 | export interface ActivityCreate extends BaseActivity { |
33 | type: 'Create' | 34 | type: 'Create' |
34 | object: VideoTorrentObject | VideoAbuseObject | ViewObject | DislikeObject | VideoCommentObject | CacheFileObject | 35 | object: VideoTorrentObject | VideoAbuseObject | ViewObject | DislikeObject | VideoCommentObject | CacheFileObject | PlaylistObject |
35 | } | 36 | } |
36 | 37 | ||
37 | export interface ActivityUpdate extends BaseActivity { | 38 | export interface ActivityUpdate extends BaseActivity { |
38 | type: 'Update' | 39 | type: 'Update' |
39 | object: VideoTorrentObject | ActivityPubActor | CacheFileObject | 40 | object: VideoTorrentObject | ActivityPubActor | CacheFileObject | PlaylistObject |
40 | } | 41 | } |
41 | 42 | ||
42 | export interface ActivityDelete extends BaseActivity { | 43 | export interface ActivityDelete extends BaseActivity { |
diff --git a/shared/models/activitypub/activitypub-actor.ts b/shared/models/activitypub/activitypub-actor.ts index 119bc22d4..5e30bf783 100644 --- a/shared/models/activitypub/activitypub-actor.ts +++ b/shared/models/activitypub/activitypub-actor.ts | |||
@@ -8,6 +8,7 @@ export interface ActivityPubActor { | |||
8 | id: string | 8 | id: string |
9 | following: string | 9 | following: string |
10 | followers: string | 10 | followers: string |
11 | playlists?: string | ||
11 | inbox: string | 12 | inbox: string |
12 | outbox: string | 13 | outbox: string |
13 | preferredUsername: string | 14 | preferredUsername: string |
diff --git a/shared/models/activitypub/objects/playlist-element-object.ts b/shared/models/activitypub/objects/playlist-element-object.ts new file mode 100644 index 000000000..b85e4fe19 --- /dev/null +++ b/shared/models/activitypub/objects/playlist-element-object.ts | |||
@@ -0,0 +1,10 @@ | |||
1 | export interface PlaylistElementObject { | ||
2 | id: string | ||
3 | type: 'PlaylistElement' | ||
4 | |||
5 | url: string | ||
6 | position: number | ||
7 | |||
8 | startTimestamp?: number | ||
9 | stopTimestamp?: number | ||
10 | } | ||
diff --git a/shared/models/activitypub/objects/playlist-object.ts b/shared/models/activitypub/objects/playlist-object.ts new file mode 100644 index 000000000..5f6733f92 --- /dev/null +++ b/shared/models/activitypub/objects/playlist-object.ts | |||
@@ -0,0 +1,23 @@ | |||
1 | import { ActivityIconObject } from './common-objects' | ||
2 | |||
3 | export interface PlaylistObject { | ||
4 | id: string | ||
5 | type: 'Playlist' | ||
6 | |||
7 | name: string | ||
8 | content: string | ||
9 | uuid: string | ||
10 | |||
11 | totalItems: number | ||
12 | attributedTo: string[] | ||
13 | |||
14 | icon: ActivityIconObject | ||
15 | |||
16 | orderedItems?: string[] | ||
17 | |||
18 | partOf?: string | ||
19 | next?: string | ||
20 | first?: string | ||
21 | |||
22 | to?: string[] | ||
23 | } | ||
diff --git a/shared/models/actors/account.model.ts b/shared/models/actors/account.model.ts index 7f1dbbc37..043a2507e 100644 --- a/shared/models/actors/account.model.ts +++ b/shared/models/actors/account.model.ts | |||
@@ -1,4 +1,5 @@ | |||
1 | import { Actor } from './actor.model' | 1 | import { Actor } from './actor.model' |
2 | import { Avatar } from '../avatars' | ||
2 | 3 | ||
3 | export interface Account extends Actor { | 4 | export interface Account extends Actor { |
4 | displayName: string | 5 | displayName: string |
@@ -6,3 +7,13 @@ export interface Account extends Actor { | |||
6 | 7 | ||
7 | userId?: number | 8 | userId?: number |
8 | } | 9 | } |
10 | |||
11 | export interface AccountSummary { | ||
12 | id: number | ||
13 | uuid: string | ||
14 | name: string | ||
15 | displayName: string | ||
16 | url: string | ||
17 | host: string | ||
18 | avatar?: Avatar | ||
19 | } | ||
diff --git a/shared/models/overviews/videos-overview.ts b/shared/models/overviews/videos-overview.ts index ee009d94c..e725f166b 100644 --- a/shared/models/overviews/videos-overview.ts +++ b/shared/models/overviews/videos-overview.ts | |||
@@ -1,8 +1,8 @@ | |||
1 | import { Video, VideoChannelAttribute, VideoConstant } from '../videos' | 1 | import { Video, VideoChannelSummary, VideoConstant } from '../videos' |
2 | 2 | ||
3 | export interface VideosOverview { | 3 | export interface VideosOverview { |
4 | channels: { | 4 | channels: { |
5 | channel: VideoChannelAttribute | 5 | channel: VideoChannelSummary |
6 | videos: Video[] | 6 | videos: Video[] |
7 | }[] | 7 | }[] |
8 | 8 | ||
diff --git a/shared/models/users/user-right.enum.ts b/shared/models/users/user-right.enum.ts index 090256bca..eaa064bd9 100644 --- a/shared/models/users/user-right.enum.ts +++ b/shared/models/users/user-right.enum.ts | |||
@@ -20,8 +20,12 @@ export enum UserRight { | |||
20 | 20 | ||
21 | REMOVE_ANY_VIDEO, | 21 | REMOVE_ANY_VIDEO, |
22 | REMOVE_ANY_VIDEO_CHANNEL, | 22 | REMOVE_ANY_VIDEO_CHANNEL, |
23 | REMOVE_ANY_VIDEO_PLAYLIST, | ||
23 | REMOVE_ANY_VIDEO_COMMENT, | 24 | REMOVE_ANY_VIDEO_COMMENT, |
25 | |||
24 | UPDATE_ANY_VIDEO, | 26 | UPDATE_ANY_VIDEO, |
27 | UPDATE_ANY_VIDEO_PLAYLIST, | ||
28 | |||
25 | SEE_ALL_VIDEOS, | 29 | SEE_ALL_VIDEOS, |
26 | CHANGE_VIDEO_OWNERSHIP | 30 | CHANGE_VIDEO_OWNERSHIP |
27 | } | 31 | } |
diff --git a/shared/models/users/user-role.ts b/shared/models/users/user-role.ts index 59c2ba106..0b6554e51 100644 --- a/shared/models/users/user-role.ts +++ b/shared/models/users/user-role.ts | |||
@@ -25,6 +25,7 @@ const userRoleRights: { [ id: number ]: UserRight[] } = { | |||
25 | UserRight.MANAGE_VIDEO_ABUSES, | 25 | UserRight.MANAGE_VIDEO_ABUSES, |
26 | UserRight.REMOVE_ANY_VIDEO, | 26 | UserRight.REMOVE_ANY_VIDEO, |
27 | UserRight.REMOVE_ANY_VIDEO_CHANNEL, | 27 | UserRight.REMOVE_ANY_VIDEO_CHANNEL, |
28 | UserRight.REMOVE_ANY_VIDEO_PLAYLIST, | ||
28 | UserRight.REMOVE_ANY_VIDEO_COMMENT, | 29 | UserRight.REMOVE_ANY_VIDEO_COMMENT, |
29 | UserRight.UPDATE_ANY_VIDEO, | 30 | UserRight.UPDATE_ANY_VIDEO, |
30 | UserRight.SEE_ALL_VIDEOS, | 31 | UserRight.SEE_ALL_VIDEOS, |
diff --git a/shared/models/videos/channel/video-channel.model.ts b/shared/models/videos/channel/video-channel.model.ts index 92918f66c..14a813f8f 100644 --- a/shared/models/videos/channel/video-channel.model.ts +++ b/shared/models/videos/channel/video-channel.model.ts | |||
@@ -1,6 +1,6 @@ | |||
1 | import { Actor } from '../../actors/actor.model' | 1 | import { Actor } from '../../actors/actor.model' |
2 | import { Video } from '../video.model' | ||
3 | import { Account } from '../../actors/index' | 2 | import { Account } from '../../actors/index' |
3 | import { Avatar } from '../../avatars' | ||
4 | 4 | ||
5 | export interface VideoChannel extends Actor { | 5 | export interface VideoChannel extends Actor { |
6 | displayName: string | 6 | displayName: string |
@@ -9,3 +9,13 @@ export interface VideoChannel extends Actor { | |||
9 | isLocal: boolean | 9 | isLocal: boolean |
10 | ownerAccount?: Account | 10 | ownerAccount?: Account |
11 | } | 11 | } |
12 | |||
13 | export interface VideoChannelSummary { | ||
14 | id: number | ||
15 | uuid: string | ||
16 | name: string | ||
17 | displayName: string | ||
18 | url: string | ||
19 | host: string | ||
20 | avatar?: Avatar | ||
21 | } | ||
diff --git a/shared/models/videos/playlist/video-playlist-create.model.ts b/shared/models/videos/playlist/video-playlist-create.model.ts new file mode 100644 index 000000000..386acbb96 --- /dev/null +++ b/shared/models/videos/playlist/video-playlist-create.model.ts | |||
@@ -0,0 +1,11 @@ | |||
1 | import { VideoPlaylistPrivacy } from './video-playlist-privacy.model' | ||
2 | |||
3 | export interface VideoPlaylistCreate { | ||
4 | displayName: string | ||
5 | description: string | ||
6 | privacy: VideoPlaylistPrivacy | ||
7 | |||
8 | videoChannelId?: number | ||
9 | |||
10 | thumbnailfile?: Blob | ||
11 | } | ||
diff --git a/shared/models/videos/playlist/video-playlist-element-create.model.ts b/shared/models/videos/playlist/video-playlist-element-create.model.ts new file mode 100644 index 000000000..9bd56a8ca --- /dev/null +++ b/shared/models/videos/playlist/video-playlist-element-create.model.ts | |||
@@ -0,0 +1,4 @@ | |||
1 | export interface VideoPlaylistElementCreate { | ||
2 | startTimestamp?: number | ||
3 | stopTimestamp?: number | ||
4 | } | ||
diff --git a/shared/models/videos/playlist/video-playlist-element-update.model.ts b/shared/models/videos/playlist/video-playlist-element-update.model.ts new file mode 100644 index 000000000..15a30fbdc --- /dev/null +++ b/shared/models/videos/playlist/video-playlist-element-update.model.ts | |||
@@ -0,0 +1,4 @@ | |||
1 | export interface VideoPlaylistElementUpdate { | ||
2 | startTimestamp?: number | ||
3 | stopTimestamp?: number | ||
4 | } | ||
diff --git a/shared/models/videos/playlist/video-playlist-privacy.model.ts b/shared/models/videos/playlist/video-playlist-privacy.model.ts new file mode 100644 index 000000000..96e5e2211 --- /dev/null +++ b/shared/models/videos/playlist/video-playlist-privacy.model.ts | |||
@@ -0,0 +1,5 @@ | |||
1 | export enum VideoPlaylistPrivacy { | ||
2 | PUBLIC = 1, | ||
3 | UNLISTED = 2, | ||
4 | PRIVATE = 3 | ||
5 | } | ||
diff --git a/shared/models/videos/playlist/video-playlist-update.model.ts b/shared/models/videos/playlist/video-playlist-update.model.ts new file mode 100644 index 000000000..c7a15c550 --- /dev/null +++ b/shared/models/videos/playlist/video-playlist-update.model.ts | |||
@@ -0,0 +1,10 @@ | |||
1 | import { VideoPlaylistPrivacy } from './video-playlist-privacy.model' | ||
2 | |||
3 | export interface VideoPlaylistUpdate { | ||
4 | displayName: string | ||
5 | description: string | ||
6 | privacy: VideoPlaylistPrivacy | ||
7 | |||
8 | videoChannelId?: number | ||
9 | thumbnailfile?: Blob | ||
10 | } | ||
diff --git a/shared/models/videos/playlist/video-playlist.model.ts b/shared/models/videos/playlist/video-playlist.model.ts new file mode 100644 index 000000000..6aa04048c --- /dev/null +++ b/shared/models/videos/playlist/video-playlist.model.ts | |||
@@ -0,0 +1,23 @@ | |||
1 | import { AccountSummary } from '../../actors/index' | ||
2 | import { VideoChannelSummary, VideoConstant } from '..' | ||
3 | import { VideoPlaylistPrivacy } from './video-playlist-privacy.model' | ||
4 | |||
5 | export interface VideoPlaylist { | ||
6 | id: number | ||
7 | uuid: string | ||
8 | isLocal: boolean | ||
9 | |||
10 | displayName: string | ||
11 | description: string | ||
12 | privacy: VideoConstant<VideoPlaylistPrivacy> | ||
13 | |||
14 | thumbnailPath: string | ||
15 | |||
16 | videosLength: number | ||
17 | |||
18 | createdAt: Date | string | ||
19 | updatedAt: Date | string | ||
20 | |||
21 | ownerAccount?: AccountSummary | ||
22 | videoChannel?: VideoChannelSummary | ||
23 | } | ||
diff --git a/shared/models/videos/video.model.ts b/shared/models/videos/video.model.ts index df800461c..6e7a6831e 100644 --- a/shared/models/videos/video.model.ts +++ b/shared/models/videos/video.model.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | import { VideoResolution, VideoState } from '../../index' | 1 | import { AccountSummary, VideoChannelSummary, VideoResolution, VideoState } from '../../index' |
2 | import { Account } from '../actors' | 2 | import { Account } from '../actors' |
3 | import { Avatar } from '../avatars/avatar.model' | 3 | import { Avatar } from '../avatars/avatar.model' |
4 | import { VideoChannel } from './channel/video-channel.model' | 4 | import { VideoChannel } from './channel/video-channel.model' |
@@ -18,26 +18,6 @@ export interface VideoFile { | |||
18 | fps: number | 18 | fps: number |
19 | } | 19 | } |
20 | 20 | ||
21 | export interface VideoChannelAttribute { | ||
22 | id: number | ||
23 | uuid: string | ||
24 | name: string | ||
25 | displayName: string | ||
26 | url: string | ||
27 | host: string | ||
28 | avatar?: Avatar | ||
29 | } | ||
30 | |||
31 | export interface AccountAttribute { | ||
32 | id: number | ||
33 | uuid: string | ||
34 | name: string | ||
35 | displayName: string | ||
36 | url: string | ||
37 | host: string | ||
38 | avatar?: Avatar | ||
39 | } | ||
40 | |||
41 | export interface Video { | 21 | export interface Video { |
42 | id: number | 22 | id: number |
43 | uuid: string | 23 | uuid: string |
@@ -68,12 +48,18 @@ export interface Video { | |||
68 | blacklisted?: boolean | 48 | blacklisted?: boolean |
69 | blacklistedReason?: string | 49 | blacklistedReason?: string |
70 | 50 | ||
71 | account: AccountAttribute | 51 | account: AccountSummary |
72 | channel: VideoChannelAttribute | 52 | channel: VideoChannelSummary |
73 | 53 | ||
74 | userHistory?: { | 54 | userHistory?: { |
75 | currentTime: number | 55 | currentTime: number |
76 | } | 56 | } |
57 | |||
58 | playlistElement?: { | ||
59 | position: number | ||
60 | startTimestamp: number | ||
61 | stopTimestamp: number | ||
62 | } | ||
77 | } | 63 | } |
78 | 64 | ||
79 | export interface VideoDetails extends Video { | 65 | export interface VideoDetails extends Video { |
diff --git a/shared/utils/videos/video-playlists.ts b/shared/utils/videos/video-playlists.ts index eb25011cb..5186d9c4f 100644 --- a/shared/utils/videos/video-playlists.ts +++ b/shared/utils/videos/video-playlists.ts | |||
@@ -1,51 +1,185 @@ | |||
1 | import { makeRawRequest } from '../requests/requests' | 1 | import { makeDeleteRequest, makeGetRequest, makePostBodyRequest, makePutBodyRequest, makeUploadRequest } from '../requests/requests' |
2 | import { sha256 } from '../../../server/helpers/core-utils' | 2 | import { VideoPlaylistCreate } from '../../models/videos/playlist/video-playlist-create.model' |
3 | import { VideoStreamingPlaylist } from '../../models/videos/video-streaming-playlist.model' | 3 | import { omit } from 'lodash' |
4 | import { expect } from 'chai' | 4 | import { VideoPlaylistUpdate } from '../../models/videos/playlist/video-playlist-update.model' |
5 | import { VideoPlaylistElementCreate } from '../../models/videos/playlist/video-playlist-element-create.model' | ||
6 | import { VideoPlaylistElementUpdate } from '../../models/videos/playlist/video-playlist-element-update.model' | ||
5 | 7 | ||
6 | function getPlaylist (url: string, statusCodeExpected = 200) { | 8 | function getVideoPlaylistsList (url: string, start: number, count: number, sort?: string) { |
7 | return makeRawRequest(url, statusCodeExpected) | 9 | const path = '/api/v1/video-playlists' |
10 | |||
11 | const query = { | ||
12 | start, | ||
13 | count, | ||
14 | sort | ||
15 | } | ||
16 | |||
17 | return makeGetRequest({ | ||
18 | url, | ||
19 | path, | ||
20 | query | ||
21 | }) | ||
8 | } | 22 | } |
9 | 23 | ||
10 | function getSegment (url: string, statusCodeExpected = 200, range?: string) { | 24 | function getVideoPlaylist (url: string, playlistId: number | string, statusCodeExpected = 200) { |
11 | return makeRawRequest(url, statusCodeExpected, range) | 25 | const path = '/api/v1/video-playlists/' + playlistId |
26 | |||
27 | return makeGetRequest({ | ||
28 | url, | ||
29 | path, | ||
30 | statusCodeExpected | ||
31 | }) | ||
12 | } | 32 | } |
13 | 33 | ||
14 | function getSegmentSha256 (url: string, statusCodeExpected = 200) { | 34 | function deleteVideoPlaylist (url: string, token: string, playlistId: number | string, statusCodeExpected = 200) { |
15 | return makeRawRequest(url, statusCodeExpected) | 35 | const path = '/api/v1/video-playlists/' + playlistId |
36 | |||
37 | return makeDeleteRequest({ | ||
38 | url, | ||
39 | path, | ||
40 | token, | ||
41 | statusCodeExpected | ||
42 | }) | ||
16 | } | 43 | } |
17 | 44 | ||
18 | async function checkSegmentHash ( | 45 | function createVideoPlaylist (options: { |
19 | baseUrlPlaylist: string, | 46 | url: string, |
20 | baseUrlSegment: string, | 47 | token: string, |
21 | videoUUID: string, | 48 | playlistAttrs: VideoPlaylistCreate, |
22 | resolution: number, | 49 | expectedStatus: number |
23 | hlsPlaylist: VideoStreamingPlaylist | 50 | }) { |
24 | ) { | 51 | const path = '/api/v1/video-playlists/' |
25 | const res = await getPlaylist(`${baseUrlPlaylist}/${videoUUID}/${resolution}.m3u8`) | 52 | |
26 | const playlist = res.text | 53 | const fields = omit(options.playlistAttrs, 'thumbnailfile') |
27 | 54 | ||
28 | const videoName = `${videoUUID}-${resolution}-fragmented.mp4` | 55 | const attaches = options.playlistAttrs.thumbnailfile |
56 | ? { thumbnailfile: options.playlistAttrs.thumbnailfile } | ||
57 | : {} | ||
29 | 58 | ||
30 | const matches = /#EXT-X-BYTERANGE:(\d+)@(\d+)/.exec(playlist) | 59 | return makeUploadRequest({ |
60 | method: 'POST', | ||
61 | url: options.url, | ||
62 | path, | ||
63 | token: options.token, | ||
64 | fields, | ||
65 | attaches, | ||
66 | statusCodeExpected: options.expectedStatus | ||
67 | }) | ||
68 | } | ||
31 | 69 | ||
32 | const length = parseInt(matches[1], 10) | 70 | function updateVideoPlaylist (options: { |
33 | const offset = parseInt(matches[2], 10) | 71 | url: string, |
34 | const range = `${offset}-${offset + length - 1}` | 72 | token: string, |
73 | playlistAttrs: VideoPlaylistUpdate, | ||
74 | expectedStatus: number | ||
75 | }) { | ||
76 | const path = '/api/v1/video-playlists/' | ||
35 | 77 | ||
36 | const res2 = await getSegment(`${baseUrlSegment}/${videoUUID}/${videoName}`, 206, `bytes=${range}`) | 78 | const fields = omit(options.playlistAttrs, 'thumbnailfile') |
37 | 79 | ||
38 | const resSha = await getSegmentSha256(hlsPlaylist.segmentsSha256Url) | 80 | const attaches = options.playlistAttrs.thumbnailfile |
81 | ? { thumbnailfile: options.playlistAttrs.thumbnailfile } | ||
82 | : {} | ||
39 | 83 | ||
40 | const sha256Server = resSha.body[ videoName ][range] | 84 | return makeUploadRequest({ |
41 | expect(sha256(res2.body)).to.equal(sha256Server) | 85 | method: 'PUT', |
86 | url: options.url, | ||
87 | path, | ||
88 | token: options.token, | ||
89 | fields, | ||
90 | attaches, | ||
91 | statusCodeExpected: options.expectedStatus | ||
92 | }) | ||
93 | } | ||
94 | |||
95 | function addVideoInPlaylist (options: { | ||
96 | url: string, | ||
97 | token: string, | ||
98 | playlistId: number | string, | ||
99 | elementAttrs: VideoPlaylistElementCreate | ||
100 | expectedStatus: number | ||
101 | }) { | ||
102 | const path = '/api/v1/video-playlists/' + options.playlistId + '/videos' | ||
103 | |||
104 | return makePostBodyRequest({ | ||
105 | url: options.url, | ||
106 | path, | ||
107 | token: options.token, | ||
108 | fields: options.elementAttrs, | ||
109 | statusCodeExpected: options.expectedStatus | ||
110 | }) | ||
111 | } | ||
112 | |||
113 | function updateVideoPlaylistElement (options: { | ||
114 | url: string, | ||
115 | token: string, | ||
116 | playlistId: number | string, | ||
117 | videoId: number | string, | ||
118 | elementAttrs: VideoPlaylistElementUpdate, | ||
119 | expectedStatus: number | ||
120 | }) { | ||
121 | const path = '/api/v1/video-playlists/' + options.playlistId + '/videos/' + options.videoId | ||
122 | |||
123 | return makePutBodyRequest({ | ||
124 | url: options.url, | ||
125 | path, | ||
126 | token: options.token, | ||
127 | fields: options.elementAttrs, | ||
128 | statusCodeExpected: options.expectedStatus | ||
129 | }) | ||
130 | } | ||
131 | |||
132 | function removeVideoFromPlaylist (options: { | ||
133 | url: string, | ||
134 | token: string, | ||
135 | playlistId: number | string, | ||
136 | videoId: number | string, | ||
137 | expectedStatus: number | ||
138 | }) { | ||
139 | const path = '/api/v1/video-playlists/' + options.playlistId + '/videos/' + options.videoId | ||
140 | |||
141 | return makeDeleteRequest({ | ||
142 | url: options.url, | ||
143 | path, | ||
144 | token: options.token, | ||
145 | statusCodeExpected: options.expectedStatus | ||
146 | }) | ||
147 | } | ||
148 | |||
149 | function reorderVideosPlaylist (options: { | ||
150 | url: string, | ||
151 | token: string, | ||
152 | playlistId: number | string, | ||
153 | elementAttrs: { | ||
154 | startPosition: number, | ||
155 | insertAfter: number, | ||
156 | reorderLength?: number | ||
157 | }, | ||
158 | expectedStatus: number | ||
159 | }) { | ||
160 | const path = '/api/v1/video-playlists/' + options.playlistId + '/videos' | ||
161 | |||
162 | return makePutBodyRequest({ | ||
163 | url: options.url, | ||
164 | path, | ||
165 | token: options.token, | ||
166 | fields: options.elementAttrs, | ||
167 | statusCodeExpected: options.expectedStatus | ||
168 | }) | ||
42 | } | 169 | } |
43 | 170 | ||
44 | // --------------------------------------------------------------------------- | 171 | // --------------------------------------------------------------------------- |
45 | 172 | ||
46 | export { | 173 | export { |
47 | getPlaylist, | 174 | getVideoPlaylistsList, |
48 | getSegment, | 175 | getVideoPlaylist, |
49 | getSegmentSha256, | 176 | |
50 | checkSegmentHash | 177 | createVideoPlaylist, |
178 | updateVideoPlaylist, | ||
179 | deleteVideoPlaylist, | ||
180 | |||
181 | addVideoInPlaylist, | ||
182 | removeVideoFromPlaylist, | ||
183 | |||
184 | reorderVideosPlaylist | ||
51 | } | 185 | } |
diff --git a/shared/utils/videos/video-streaming-playlists.ts b/shared/utils/videos/video-streaming-playlists.ts new file mode 100644 index 000000000..eb25011cb --- /dev/null +++ b/shared/utils/videos/video-streaming-playlists.ts | |||
@@ -0,0 +1,51 @@ | |||
1 | import { makeRawRequest } from '../requests/requests' | ||
2 | import { sha256 } from '../../../server/helpers/core-utils' | ||
3 | import { VideoStreamingPlaylist } from '../../models/videos/video-streaming-playlist.model' | ||
4 | import { expect } from 'chai' | ||
5 | |||
6 | function getPlaylist (url: string, statusCodeExpected = 200) { | ||
7 | return makeRawRequest(url, statusCodeExpected) | ||
8 | } | ||
9 | |||
10 | function getSegment (url: string, statusCodeExpected = 200, range?: string) { | ||
11 | return makeRawRequest(url, statusCodeExpected, range) | ||
12 | } | ||
13 | |||
14 | function getSegmentSha256 (url: string, statusCodeExpected = 200) { | ||
15 | return makeRawRequest(url, statusCodeExpected) | ||
16 | } | ||
17 | |||
18 | async function checkSegmentHash ( | ||
19 | baseUrlPlaylist: string, | ||
20 | baseUrlSegment: string, | ||
21 | videoUUID: string, | ||
22 | resolution: number, | ||
23 | hlsPlaylist: VideoStreamingPlaylist | ||
24 | ) { | ||
25 | const res = await getPlaylist(`${baseUrlPlaylist}/${videoUUID}/${resolution}.m3u8`) | ||
26 | const playlist = res.text | ||
27 | |||
28 | const videoName = `${videoUUID}-${resolution}-fragmented.mp4` | ||
29 | |||
30 | const matches = /#EXT-X-BYTERANGE:(\d+)@(\d+)/.exec(playlist) | ||
31 | |||
32 | const length = parseInt(matches[1], 10) | ||
33 | const offset = parseInt(matches[2], 10) | ||
34 | const range = `${offset}-${offset + length - 1}` | ||
35 | |||
36 | const res2 = await getSegment(`${baseUrlSegment}/${videoUUID}/${videoName}`, 206, `bytes=${range}`) | ||
37 | |||
38 | const resSha = await getSegmentSha256(hlsPlaylist.segmentsSha256Url) | ||
39 | |||
40 | const sha256Server = resSha.body[ videoName ][range] | ||
41 | expect(sha256(res2.body)).to.equal(sha256Server) | ||
42 | } | ||
43 | |||
44 | // --------------------------------------------------------------------------- | ||
45 | |||
46 | export { | ||
47 | getPlaylist, | ||
48 | getSegment, | ||
49 | getSegmentSha256, | ||
50 | checkSegmentHash | ||
51 | } | ||
diff --git a/shared/utils/videos/videos.ts b/shared/utils/videos/videos.ts index b3d24bc53..2c09f0086 100644 --- a/shared/utils/videos/videos.ts +++ b/shared/utils/videos/videos.ts | |||
@@ -223,6 +223,28 @@ function getVideoChannelVideos ( | |||
223 | }) | 223 | }) |
224 | } | 224 | } |
225 | 225 | ||
226 | function getPlaylistVideos ( | ||
227 | url: string, | ||
228 | accessToken: string, | ||
229 | playlistId: number | string, | ||
230 | start: number, | ||
231 | count: number, | ||
232 | query: { nsfw?: boolean } = {} | ||
233 | ) { | ||
234 | const path = '/api/v1/video-playlists/' + playlistId + '/videos' | ||
235 | |||
236 | return makeGetRequest({ | ||
237 | url, | ||
238 | path, | ||
239 | query: immutableAssign(query, { | ||
240 | start, | ||
241 | count | ||
242 | }), | ||
243 | token: accessToken, | ||
244 | statusCodeExpected: 200 | ||
245 | }) | ||
246 | } | ||
247 | |||
226 | function getVideosListPagination (url: string, start: number, count: number, sort?: string) { | 248 | function getVideosListPagination (url: string, start: number, count: number, sort?: string) { |
227 | const path = '/api/v1/videos' | 249 | const path = '/api/v1/videos' |
228 | 250 | ||
@@ -601,5 +623,6 @@ export { | |||
601 | parseTorrentVideo, | 623 | parseTorrentVideo, |
602 | getLocalVideos, | 624 | getLocalVideos, |
603 | completeVideoCheck, | 625 | completeVideoCheck, |
604 | checkVideoFilesWereRemoved | 626 | checkVideoFilesWereRemoved, |
627 | getPlaylistVideos | ||
605 | } | 628 | } |