From 092092969633bbcf6d4891a083ea497a7d5c3154 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Tue, 29 Jan 2019 08:37:25 +0100 Subject: Add hls support on server --- server/models/video/video-streaming-playlist.ts | 154 ++++++++++++++++++++++++ 1 file changed, 154 insertions(+) create mode 100644 server/models/video/video-streaming-playlist.ts (limited to 'server/models/video/video-streaming-playlist.ts') diff --git a/server/models/video/video-streaming-playlist.ts b/server/models/video/video-streaming-playlist.ts new file mode 100644 index 000000000..bce537781 --- /dev/null +++ b/server/models/video/video-streaming-playlist.ts @@ -0,0 +1,154 @@ +import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, HasMany, Is, Model, Table, UpdatedAt } from 'sequelize-typescript' +import { isVideoFileInfoHashValid } from '../../helpers/custom-validators/videos' +import { throwIfNotValid } from '../utils' +import { VideoModel } from './video' +import * as Sequelize from 'sequelize' +import { VideoRedundancyModel } from '../redundancy/video-redundancy' +import { VideoStreamingPlaylistType } from '../../../shared/models/videos/video-streaming-playlist.type' +import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' +import { CONSTRAINTS_FIELDS, STATIC_PATHS } from '../../initializers' +import { VideoFileModel } from './video-file' +import { join } from 'path' +import { sha1 } from '../../helpers/core-utils' +import { isArrayOf } from '../../helpers/custom-validators/misc' + +@Table({ + tableName: 'videoStreamingPlaylist', + indexes: [ + { + fields: [ 'videoId' ] + }, + { + fields: [ 'videoId', 'type' ], + unique: true + }, + { + fields: [ 'p2pMediaLoaderInfohashes' ], + using: 'gin' + } + ] +}) +export class VideoStreamingPlaylistModel extends Model { + @CreatedAt + createdAt: Date + + @UpdatedAt + updatedAt: Date + + @AllowNull(false) + @Column + type: VideoStreamingPlaylistType + + @AllowNull(false) + @Is('PlaylistUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'playlist url')) + @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEOS.URL.max)) + playlistUrl: string + + @AllowNull(false) + @Is('VideoStreamingPlaylistInfoHashes', value => throwIfNotValid(value, v => isArrayOf(v, isVideoFileInfoHashValid), 'info hashes')) + @Column(DataType.ARRAY(DataType.STRING)) + p2pMediaLoaderInfohashes: string[] + + @AllowNull(false) + @Is('VideoStreamingSegmentsSha256Url', value => throwIfNotValid(value, isActivityPubUrlValid, 'segments sha256 url')) + @Column + segmentsSha256Url: string + + @ForeignKey(() => VideoModel) + @Column + videoId: number + + @BelongsTo(() => VideoModel, { + foreignKey: { + allowNull: false + }, + onDelete: 'CASCADE' + }) + Video: VideoModel + + @HasMany(() => VideoRedundancyModel, { + foreignKey: { + allowNull: false + }, + onDelete: 'CASCADE', + hooks: true + }) + RedundancyVideos: VideoRedundancyModel[] + + static doesInfohashExist (infoHash: string) { + const query = 'SELECT 1 FROM "videoStreamingPlaylist" WHERE $infoHash = ANY("p2pMediaLoaderInfohashes") LIMIT 1' + const options = { + type: Sequelize.QueryTypes.SELECT, + bind: { infoHash }, + raw: true + } + + return VideoModel.sequelize.query(query, options) + .then(results => { + return results.length === 1 + }) + } + + static buildP2PMediaLoaderInfoHashes (playlistUrl: string, videoFiles: VideoFileModel[]) { + const hashes: string[] = [] + + // https://github.com/Novage/p2p-media-loader/blob/master/p2p-media-loader-core/lib/p2p-media-manager.ts#L97 + for (let i = 0; i < videoFiles.length; i++) { + hashes.push(sha1(`1${playlistUrl}+V${i}`)) + } + + return hashes + } + + static loadWithVideo (id: number) { + const options = { + include: [ + { + model: VideoModel.unscoped(), + required: true + } + ] + } + + return VideoStreamingPlaylistModel.findById(id, options) + } + + static getHlsPlaylistFilename (resolution: number) { + return resolution + '.m3u8' + } + + static getMasterHlsPlaylistFilename () { + return 'master.m3u8' + } + + static getHlsSha256SegmentsFilename () { + return 'segments-sha256.json' + } + + static getHlsMasterPlaylistStaticPath (videoUUID: string) { + return join(STATIC_PATHS.PLAYLISTS.HLS, videoUUID, VideoStreamingPlaylistModel.getMasterHlsPlaylistFilename()) + } + + static getHlsPlaylistStaticPath (videoUUID: string, resolution: number) { + return join(STATIC_PATHS.PLAYLISTS.HLS, videoUUID, VideoStreamingPlaylistModel.getHlsPlaylistFilename(resolution)) + } + + static getHlsSha256SegmentsStaticPath (videoUUID: string) { + return join(STATIC_PATHS.PLAYLISTS.HLS, videoUUID, VideoStreamingPlaylistModel.getHlsSha256SegmentsFilename()) + } + + getStringType () { + if (this.type === VideoStreamingPlaylistType.HLS) return 'hls' + + return 'unknown' + } + + getVideoRedundancyUrl (baseUrlHttp: string) { + return baseUrlHttp + STATIC_PATHS.REDUNDANCY + this.getStringType() + '/' + this.Video.uuid + } + + hasSameUniqueKeysThan (other: VideoStreamingPlaylistModel) { + return this.type === other.type && + this.videoId === other.videoId + } +} -- cgit v1.2.3 From 4c280004ce62bf11ddb091854c28f1e1d54a54d6 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Thu, 7 Feb 2019 15:08:19 +0100 Subject: Use a single file instead of segments for HLS --- server/models/video/video-streaming-playlist.ts | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'server/models/video/video-streaming-playlist.ts') diff --git a/server/models/video/video-streaming-playlist.ts b/server/models/video/video-streaming-playlist.ts index bce537781..bf6f7b0c4 100644 --- a/server/models/video/video-streaming-playlist.ts +++ b/server/models/video/video-streaming-playlist.ts @@ -125,6 +125,10 @@ export class VideoStreamingPlaylistModel extends Model