aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/models/video/video.ts
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2019-01-29 08:37:25 +0100
committerChocobozzz <chocobozzz@cpy.re>2019-02-11 09:13:02 +0100
commit092092969633bbcf6d4891a083ea497a7d5c3154 (patch)
tree69e82fe4f60c444cca216830e96afe143a9dac71 /server/models/video/video.ts
parent4348a27d252a3349bafa7ef4859c0e2cf060c255 (diff)
downloadPeerTube-092092969633bbcf6d4891a083ea497a7d5c3154.tar.gz
PeerTube-092092969633bbcf6d4891a083ea497a7d5c3154.tar.zst
PeerTube-092092969633bbcf6d4891a083ea497a7d5c3154.zip
Add hls support on server
Diffstat (limited to 'server/models/video/video.ts')
-rw-r--r--server/models/video/video.ts179
1 files changed, 148 insertions, 31 deletions
diff --git a/server/models/video/video.ts b/server/models/video/video.ts
index 80a6c7832..702260772 100644
--- a/server/models/video/video.ts
+++ b/server/models/video/video.ts
@@ -52,7 +52,7 @@ import {
52 ACTIVITY_PUB, 52 ACTIVITY_PUB,
53 API_VERSION, 53 API_VERSION,
54 CONFIG, 54 CONFIG,
55 CONSTRAINTS_FIELDS, 55 CONSTRAINTS_FIELDS, HLS_PLAYLIST_DIRECTORY, HLS_REDUNDANCY_DIRECTORY,
56 PREVIEWS_SIZE, 56 PREVIEWS_SIZE,
57 REMOTE_SCHEME, 57 REMOTE_SCHEME,
58 STATIC_DOWNLOAD_PATHS, 58 STATIC_DOWNLOAD_PATHS,
@@ -95,6 +95,7 @@ import * as validator from 'validator'
95import { UserVideoHistoryModel } from '../account/user-video-history' 95import { UserVideoHistoryModel } from '../account/user-video-history'
96import { UserModel } from '../account/user' 96import { UserModel } from '../account/user'
97import { VideoImportModel } from './video-import' 97import { VideoImportModel } from './video-import'
98import { VideoStreamingPlaylistModel } from './video-streaming-playlist'
98 99
99// FIXME: Define indexes here because there is an issue with TS and Sequelize.literal when called directly in the annotation 100// FIXME: Define indexes here because there is an issue with TS and Sequelize.literal when called directly in the annotation
100const indexes: Sequelize.DefineIndexesOptions[] = [ 101const indexes: Sequelize.DefineIndexesOptions[] = [
@@ -159,7 +160,9 @@ export enum ScopeNames {
159 WITH_FILES = 'WITH_FILES', 160 WITH_FILES = 'WITH_FILES',
160 WITH_SCHEDULED_UPDATE = 'WITH_SCHEDULED_UPDATE', 161 WITH_SCHEDULED_UPDATE = 'WITH_SCHEDULED_UPDATE',
161 WITH_BLACKLISTED = 'WITH_BLACKLISTED', 162 WITH_BLACKLISTED = 'WITH_BLACKLISTED',
162 WITH_USER_HISTORY = 'WITH_USER_HISTORY' 163 WITH_USER_HISTORY = 'WITH_USER_HISTORY',
164 WITH_STREAMING_PLAYLISTS = 'WITH_STREAMING_PLAYLISTS',
165 WITH_USER_ID = 'WITH_USER_ID'
163} 166}
164 167
165type ForAPIOptions = { 168type ForAPIOptions = {
@@ -463,6 +466,22 @@ type AvailableForListIDsOptions = {
463 466
464 return query 467 return query
465 }, 468 },
469 [ ScopeNames.WITH_USER_ID ]: {
470 include: [
471 {
472 attributes: [ 'accountId' ],
473 model: () => VideoChannelModel.unscoped(),
474 required: true,
475 include: [
476 {
477 attributes: [ 'userId' ],
478 model: () => AccountModel.unscoped(),
479 required: true
480 }
481 ]
482 }
483 ]
484 },
466 [ ScopeNames.WITH_ACCOUNT_DETAILS ]: { 485 [ ScopeNames.WITH_ACCOUNT_DETAILS ]: {
467 include: [ 486 include: [
468 { 487 {
@@ -527,22 +546,55 @@ type AvailableForListIDsOptions = {
527 } 546 }
528 ] 547 ]
529 }, 548 },
530 [ ScopeNames.WITH_FILES ]: { 549 [ ScopeNames.WITH_FILES ]: (withRedundancies = false) => {
531 include: [ 550 let subInclude: any[] = []
532 { 551
533 model: () => VideoFileModel.unscoped(), 552 if (withRedundancies === true) {
534 // FIXME: typings 553 subInclude = [
535 [ 'separate' as any ]: true, // We may have multiple files, having multiple redundancies so let's separate this join 554 {
536 required: false, 555 attributes: [ 'fileUrl' ],
537 include: [ 556 model: VideoRedundancyModel.unscoped(),
538 { 557 required: false
539 attributes: [ 'fileUrl' ], 558 }
540 model: () => VideoRedundancyModel.unscoped(), 559 ]
541 required: false 560 }
542 } 561
543 ] 562 return {
544 } 563 include: [
545 ] 564 {
565 model: VideoFileModel.unscoped(),
566 // FIXME: typings
567 [ 'separate' as any ]: true, // We may have multiple files, having multiple redundancies so let's separate this join
568 required: false,
569 include: subInclude
570 }
571 ]
572 }
573 },
574 [ ScopeNames.WITH_STREAMING_PLAYLISTS ]: (withRedundancies = false) => {
575 let subInclude: any[] = []
576
577 if (withRedundancies === true) {
578 subInclude = [
579 {
580 attributes: [ 'fileUrl' ],
581 model: VideoRedundancyModel.unscoped(),
582 required: false
583 }
584 ]
585 }
586
587 return {
588 include: [
589 {
590 model: VideoStreamingPlaylistModel.unscoped(),
591 // FIXME: typings
592 [ 'separate' as any ]: true, // We may have multiple streaming playlists, having multiple redundancies so let's separate this join
593 required: false,
594 include: subInclude
595 }
596 ]
597 }
546 }, 598 },
547 [ ScopeNames.WITH_SCHEDULED_UPDATE ]: { 599 [ ScopeNames.WITH_SCHEDULED_UPDATE ]: {
548 include: [ 600 include: [
@@ -722,6 +774,16 @@ export class VideoModel extends Model<VideoModel> {
722 }) 774 })
723 VideoFiles: VideoFileModel[] 775 VideoFiles: VideoFileModel[]
724 776
777 @HasMany(() => VideoStreamingPlaylistModel, {
778 foreignKey: {
779 name: 'videoId',
780 allowNull: false
781 },
782 hooks: true,
783 onDelete: 'cascade'
784 })
785 VideoStreamingPlaylists: VideoStreamingPlaylistModel[]
786
725 @HasMany(() => VideoShareModel, { 787 @HasMany(() => VideoShareModel, {
726 foreignKey: { 788 foreignKey: {
727 name: 'videoId', 789 name: 'videoId',
@@ -847,6 +909,9 @@ export class VideoModel extends Model<VideoModel> {
847 tasks.push(instance.removeFile(file)) 909 tasks.push(instance.removeFile(file))
848 tasks.push(instance.removeTorrent(file)) 910 tasks.push(instance.removeTorrent(file))
849 }) 911 })
912
913 // Remove playlists file
914 tasks.push(instance.removeStreamingPlaylist())
850 } 915 }
851 916
852 // Do not wait video deletion because we could be in a transaction 917 // Do not wait video deletion because we could be in a transaction
@@ -858,10 +923,6 @@ export class VideoModel extends Model<VideoModel> {
858 return undefined 923 return undefined
859 } 924 }
860 925
861 static list () {
862 return VideoModel.scope(ScopeNames.WITH_FILES).findAll()
863 }
864
865 static listLocal () { 926 static listLocal () {
866 const query = { 927 const query = {
867 where: { 928 where: {
@@ -869,7 +930,7 @@ export class VideoModel extends Model<VideoModel> {
869 } 930 }
870 } 931 }
871 932
872 return VideoModel.scope(ScopeNames.WITH_FILES).findAll(query) 933 return VideoModel.scope([ ScopeNames.WITH_FILES, ScopeNames.WITH_STREAMING_PLAYLISTS ]).findAll(query)
873 } 934 }
874 935
875 static listAllAndSharedByActorForOutbox (actorId: number, start: number, count: number) { 936 static listAllAndSharedByActorForOutbox (actorId: number, start: number, count: number) {
@@ -1200,6 +1261,16 @@ export class VideoModel extends Model<VideoModel> {
1200 return VideoModel.findOne(options) 1261 return VideoModel.findOne(options)
1201 } 1262 }
1202 1263
1264 static loadWithRights (id: number | string, t?: Sequelize.Transaction) {
1265 const where = VideoModel.buildWhereIdOrUUID(id)
1266 const options = {
1267 where,
1268 transaction: t
1269 }
1270
1271 return VideoModel.scope([ ScopeNames.WITH_BLACKLISTED, ScopeNames.WITH_USER_ID ]).findOne(options)
1272 }
1273
1203 static loadOnlyId (id: number | string, t?: Sequelize.Transaction) { 1274 static loadOnlyId (id: number | string, t?: Sequelize.Transaction) {
1204 const where = VideoModel.buildWhereIdOrUUID(id) 1275 const where = VideoModel.buildWhereIdOrUUID(id)
1205 1276
@@ -1212,8 +1283,8 @@ export class VideoModel extends Model<VideoModel> {
1212 return VideoModel.findOne(options) 1283 return VideoModel.findOne(options)
1213 } 1284 }
1214 1285
1215 static loadWithFile (id: number, t?: Sequelize.Transaction, logging?: boolean) { 1286 static loadWithFiles (id: number, t?: Sequelize.Transaction, logging?: boolean) {
1216 return VideoModel.scope(ScopeNames.WITH_FILES) 1287 return VideoModel.scope([ ScopeNames.WITH_FILES, ScopeNames.WITH_STREAMING_PLAYLISTS ])
1217 .findById(id, { transaction: t, logging }) 1288 .findById(id, { transaction: t, logging })
1218 } 1289 }
1219 1290
@@ -1224,9 +1295,7 @@ export class VideoModel extends Model<VideoModel> {
1224 } 1295 }
1225 } 1296 }
1226 1297
1227 return VideoModel 1298 return VideoModel.findOne(options)
1228 .scope([ ScopeNames.WITH_FILES ])
1229 .findOne(options)
1230 } 1299 }
1231 1300
1232 static loadByUrl (url: string, transaction?: Sequelize.Transaction) { 1301 static loadByUrl (url: string, transaction?: Sequelize.Transaction) {
@@ -1248,7 +1317,11 @@ export class VideoModel extends Model<VideoModel> {
1248 transaction 1317 transaction
1249 } 1318 }
1250 1319
1251 return VideoModel.scope([ ScopeNames.WITH_ACCOUNT_DETAILS, ScopeNames.WITH_FILES ]).findOne(query) 1320 return VideoModel.scope([
1321 ScopeNames.WITH_ACCOUNT_DETAILS,
1322 ScopeNames.WITH_FILES,
1323 ScopeNames.WITH_STREAMING_PLAYLISTS
1324 ]).findOne(query)
1252 } 1325 }
1253 1326
1254 static loadAndPopulateAccountAndServerAndTags (id: number | string, t?: Sequelize.Transaction, userId?: number) { 1327 static loadAndPopulateAccountAndServerAndTags (id: number | string, t?: Sequelize.Transaction, userId?: number) {
@@ -1263,9 +1336,37 @@ export class VideoModel extends Model<VideoModel> {
1263 const scopes = [ 1336 const scopes = [
1264 ScopeNames.WITH_TAGS, 1337 ScopeNames.WITH_TAGS,
1265 ScopeNames.WITH_BLACKLISTED, 1338 ScopeNames.WITH_BLACKLISTED,
1339 ScopeNames.WITH_ACCOUNT_DETAILS,
1340 ScopeNames.WITH_SCHEDULED_UPDATE,
1266 ScopeNames.WITH_FILES, 1341 ScopeNames.WITH_FILES,
1342 ScopeNames.WITH_STREAMING_PLAYLISTS
1343 ]
1344
1345 if (userId) {
1346 scopes.push({ method: [ ScopeNames.WITH_USER_HISTORY, userId ] } as any) // FIXME: typings
1347 }
1348
1349 return VideoModel
1350 .scope(scopes)
1351 .findOne(options)
1352 }
1353
1354 static loadForGetAPI (id: number | string, t?: Sequelize.Transaction, userId?: number) {
1355 const where = VideoModel.buildWhereIdOrUUID(id)
1356
1357 const options = {
1358 order: [ [ 'Tags', 'name', 'ASC' ] ],
1359 where,
1360 transaction: t
1361 }
1362
1363 const scopes = [
1364 ScopeNames.WITH_TAGS,
1365 ScopeNames.WITH_BLACKLISTED,
1267 ScopeNames.WITH_ACCOUNT_DETAILS, 1366 ScopeNames.WITH_ACCOUNT_DETAILS,
1268 ScopeNames.WITH_SCHEDULED_UPDATE 1367 ScopeNames.WITH_SCHEDULED_UPDATE,
1368 { method: [ ScopeNames.WITH_FILES, true ] } as any, // FIXME: typings
1369 { method: [ ScopeNames.WITH_STREAMING_PLAYLISTS, true ] } as any // FIXME: typings
1269 ] 1370 ]
1270 1371
1271 if (userId) { 1372 if (userId) {
@@ -1612,6 +1713,14 @@ export class VideoModel extends Model<VideoModel> {
1612 .catch(err => logger.warn('Cannot delete torrent %s.', torrentPath, { err })) 1713 .catch(err => logger.warn('Cannot delete torrent %s.', torrentPath, { err }))
1613 } 1714 }
1614 1715
1716 removeStreamingPlaylist (isRedundancy = false) {
1717 const baseDir = isRedundancy ? HLS_REDUNDANCY_DIRECTORY : HLS_PLAYLIST_DIRECTORY
1718
1719 const filePath = join(baseDir, this.uuid)
1720 return remove(filePath)
1721 .catch(err => logger.warn('Cannot delete playlist directory %s.', filePath, { err }))
1722 }
1723
1615 isOutdated () { 1724 isOutdated () {
1616 if (this.isOwned()) return false 1725 if (this.isOwned()) return false
1617 1726
@@ -1646,7 +1755,7 @@ export class VideoModel extends Model<VideoModel> {
1646 1755
1647 generateMagnetUri (videoFile: VideoFileModel, baseUrlHttp: string, baseUrlWs: string) { 1756 generateMagnetUri (videoFile: VideoFileModel, baseUrlHttp: string, baseUrlWs: string) {
1648 const xs = this.getTorrentUrl(videoFile, baseUrlHttp) 1757 const xs = this.getTorrentUrl(videoFile, baseUrlHttp)
1649 const announce = [ baseUrlWs + '/tracker/socket', baseUrlHttp + '/tracker/announce' ] 1758 const announce = this.getTrackerUrls(baseUrlHttp, baseUrlWs)
1650 let urlList = [ this.getVideoFileUrl(videoFile, baseUrlHttp) ] 1759 let urlList = [ this.getVideoFileUrl(videoFile, baseUrlHttp) ]
1651 1760
1652 const redundancies = videoFile.RedundancyVideos 1761 const redundancies = videoFile.RedundancyVideos
@@ -1663,6 +1772,10 @@ export class VideoModel extends Model<VideoModel> {
1663 return magnetUtil.encode(magnetHash) 1772 return magnetUtil.encode(magnetHash)
1664 } 1773 }
1665 1774
1775 getTrackerUrls (baseUrlHttp: string, baseUrlWs: string) {
1776 return [ baseUrlWs + '/tracker/socket', baseUrlHttp + '/tracker/announce' ]
1777 }
1778
1666 getThumbnailUrl (baseUrlHttp: string) { 1779 getThumbnailUrl (baseUrlHttp: string) {
1667 return baseUrlHttp + STATIC_PATHS.THUMBNAILS + this.getThumbnailName() 1780 return baseUrlHttp + STATIC_PATHS.THUMBNAILS + this.getThumbnailName()
1668 } 1781 }
@@ -1686,4 +1799,8 @@ export class VideoModel extends Model<VideoModel> {
1686 getVideoFileDownloadUrl (videoFile: VideoFileModel, baseUrlHttp: string) { 1799 getVideoFileDownloadUrl (videoFile: VideoFileModel, baseUrlHttp: string) {
1687 return baseUrlHttp + STATIC_DOWNLOAD_PATHS.VIDEOS + this.getVideoFilename(videoFile) 1800 return baseUrlHttp + STATIC_DOWNLOAD_PATHS.VIDEOS + this.getVideoFilename(videoFile)
1688 } 1801 }
1802
1803 getBandwidthBits (videoFile: VideoFileModel) {
1804 return Math.ceil((videoFile.size * 8) / this.duration)
1805 }
1689} 1806}