Add modal to display live information
This commit is contained in:
parent
31c82cd914
commit
d846d99c6c
18 changed files with 183 additions and 40 deletions
|
@ -0,0 +1,33 @@
|
||||||
|
<ng-template #modal let-close="close" let-dismiss="dismiss">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h4 i18n class="modal-title">Live information</h4>
|
||||||
|
|
||||||
|
<my-global-icon iconName="cross" aria-label="Close" role="button" (click)="dismiss()"></my-global-icon>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="liveVideoRTMPUrl" i18n>Live RTMP Url</label>
|
||||||
|
<my-input-readonly-copy id="liveVideoRTMPUrl" [value]="rtmpUrl"></my-input-readonly-copy>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="liveVideoStreamKey" i18n>Live stream key</label>
|
||||||
|
<my-input-readonly-copy id="liveVideoStreamKey" [value]="streamKey"></my-input-readonly-copy>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-footer">
|
||||||
|
<div class="form-group inputs">
|
||||||
|
<input
|
||||||
|
type="button" role="button" i18n-value value="Close" class="action-button action-button-cancel"
|
||||||
|
(click)="dismiss()"
|
||||||
|
>
|
||||||
|
|
||||||
|
<my-edit-button
|
||||||
|
i18n-label label="Update live settings"
|
||||||
|
[routerLink]="[ '/videos', 'update', video.uuid ]" (click)="dismiss()"
|
||||||
|
></my-edit-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
|
@ -0,0 +1,40 @@
|
||||||
|
import { Component, ElementRef, ViewChild } from '@angular/core'
|
||||||
|
import { LiveVideoService, Video } from '@app/shared/shared-main'
|
||||||
|
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'my-live-stream-information',
|
||||||
|
templateUrl: './live-stream-information.component.html',
|
||||||
|
styleUrls: [ './live-stream-information.component.scss' ]
|
||||||
|
})
|
||||||
|
export class LiveStreamInformationComponent {
|
||||||
|
@ViewChild('modal', { static: true }) modal: ElementRef
|
||||||
|
|
||||||
|
video: Video
|
||||||
|
rtmpUrl = ''
|
||||||
|
streamKey = ''
|
||||||
|
|
||||||
|
constructor (
|
||||||
|
private modalService: NgbModal,
|
||||||
|
private liveVideoService: LiveVideoService
|
||||||
|
) { }
|
||||||
|
|
||||||
|
show (video: Video) {
|
||||||
|
this.video = video
|
||||||
|
this.rtmpUrl = ''
|
||||||
|
this.streamKey = ''
|
||||||
|
|
||||||
|
this.loadLiveInfo(video)
|
||||||
|
|
||||||
|
this.modalService
|
||||||
|
.open(this.modal, { centered: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
private loadLiveInfo (video: Video) {
|
||||||
|
this.liveVideoService.getVideoLive(video.id)
|
||||||
|
.subscribe(live => {
|
||||||
|
this.rtmpUrl = live.rtmpUrl
|
||||||
|
this.streamKey = live.streamKey
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,7 +16,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="modal-footer inputs">
|
<div class="modal-footer">
|
||||||
<div class="form-group inputs">
|
<div class="form-group inputs">
|
||||||
<input
|
<input
|
||||||
type="button" role="button" i18n-value value="Cancel" class="action-button action-button-cancel"
|
type="button" role="button" i18n-value value="Cancel" class="action-button action-button-cancel"
|
|
@ -0,0 +1,10 @@
|
||||||
|
@import '_variables';
|
||||||
|
@import '_mixins';
|
||||||
|
|
||||||
|
p-autocomplete {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
|
@ -34,18 +34,13 @@
|
||||||
|
|
||||||
<ng-template ptTemplate="rowButtons" let-video>
|
<ng-template ptTemplate="rowButtons" let-video>
|
||||||
<div class="action-button">
|
<div class="action-button">
|
||||||
<my-delete-button label (click)="deleteVideo(video)"></my-delete-button>
|
|
||||||
|
|
||||||
<my-edit-button label [routerLink]="[ '/videos', 'update', video.uuid ]"></my-edit-button>
|
<my-edit-button label [routerLink]="[ '/videos', 'update', video.uuid ]"></my-edit-button>
|
||||||
|
|
||||||
<my-button i18n-label label="Change ownership"
|
<my-action-dropdown [actions]="videoActions" [entry]="{ video: video }"></my-action-dropdown>
|
||||||
className="action-button-change-ownership grey-button"
|
|
||||||
icon="ownership-change"
|
|
||||||
(click)="changeOwnership($event, video)"
|
|
||||||
></my-button>
|
|
||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</my-videos-selection>
|
</my-videos-selection>
|
||||||
|
|
||||||
|
|
||||||
<my-video-change-ownership #videoChangeOwnershipModal></my-video-change-ownership>
|
<my-video-change-ownership #videoChangeOwnershipModal></my-video-change-ownership>
|
||||||
|
<my-live-stream-information #liveStreamInformationModal></my-live-stream-information>
|
||||||
|
|
|
@ -5,10 +5,11 @@ import { ActivatedRoute, Router } from '@angular/router'
|
||||||
import { AuthService, ComponentPagination, ConfirmService, Notifier, ScreenService, ServerService } from '@app/core'
|
import { AuthService, ComponentPagination, ConfirmService, Notifier, ScreenService, ServerService } from '@app/core'
|
||||||
import { DisableForReuseHook } from '@app/core/routing/disable-for-reuse-hook'
|
import { DisableForReuseHook } from '@app/core/routing/disable-for-reuse-hook'
|
||||||
import { immutableAssign } from '@app/helpers'
|
import { immutableAssign } from '@app/helpers'
|
||||||
import { Video, VideoService } from '@app/shared/shared-main'
|
import { DropdownAction, Video, VideoService } from '@app/shared/shared-main'
|
||||||
import { MiniatureDisplayOptions, OwnerDisplayType, SelectionType, VideosSelectionComponent } from '@app/shared/shared-video-miniature'
|
import { MiniatureDisplayOptions, OwnerDisplayType, SelectionType, VideosSelectionComponent } from '@app/shared/shared-video-miniature'
|
||||||
import { VideoSortField } from '@shared/models'
|
import { VideoSortField } from '@shared/models'
|
||||||
import { VideoChangeOwnershipComponent } from './video-change-ownership/video-change-ownership.component'
|
import { VideoChangeOwnershipComponent } from './modals/video-change-ownership.component'
|
||||||
|
import { LiveStreamInformationComponent } from './modals/live-stream-information.component'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'my-account-videos',
|
selector: 'my-account-videos',
|
||||||
|
@ -18,6 +19,7 @@ import { VideoChangeOwnershipComponent } from './video-change-ownership/video-ch
|
||||||
export class MyAccountVideosComponent implements OnInit, DisableForReuseHook {
|
export class MyAccountVideosComponent implements OnInit, DisableForReuseHook {
|
||||||
@ViewChild('videosSelection', { static: true }) videosSelection: VideosSelectionComponent
|
@ViewChild('videosSelection', { static: true }) videosSelection: VideosSelectionComponent
|
||||||
@ViewChild('videoChangeOwnershipModal', { static: true }) videoChangeOwnershipModal: VideoChangeOwnershipComponent
|
@ViewChild('videoChangeOwnershipModal', { static: true }) videoChangeOwnershipModal: VideoChangeOwnershipComponent
|
||||||
|
@ViewChild('liveStreamInformationModal', { static: true }) liveStreamInformationModal: LiveStreamInformationComponent
|
||||||
|
|
||||||
titlePage: string
|
titlePage: string
|
||||||
selection: SelectionType = {}
|
selection: SelectionType = {}
|
||||||
|
@ -37,6 +39,8 @@ export class MyAccountVideosComponent implements OnInit, DisableForReuseHook {
|
||||||
}
|
}
|
||||||
ownerDisplayType: OwnerDisplayType = 'videoChannel'
|
ownerDisplayType: OwnerDisplayType = 'videoChannel'
|
||||||
|
|
||||||
|
videoActions: DropdownAction<{ video: Video }>[] = []
|
||||||
|
|
||||||
videos: Video[] = []
|
videos: Video[] = []
|
||||||
videosSearch: string
|
videosSearch: string
|
||||||
videosSearchChanged = new Subject<string>()
|
videosSearchChanged = new Subject<string>()
|
||||||
|
@ -56,6 +60,8 @@ export class MyAccountVideosComponent implements OnInit, DisableForReuseHook {
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit () {
|
ngOnInit () {
|
||||||
|
this.buildActions()
|
||||||
|
|
||||||
this.videosSearchChanged
|
this.videosSearchChanged
|
||||||
.pipe(debounceTime(500))
|
.pipe(debounceTime(500))
|
||||||
.subscribe(() => {
|
.subscribe(() => {
|
||||||
|
@ -138,12 +144,36 @@ export class MyAccountVideosComponent implements OnInit, DisableForReuseHook {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
changeOwnership (event: Event, video: Video) {
|
changeOwnership (video: Video) {
|
||||||
event.preventDefault()
|
|
||||||
this.videoChangeOwnershipModal.show(video)
|
this.videoChangeOwnershipModal.show(video)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
displayLiveInformation (video: Video) {
|
||||||
|
this.liveStreamInformationModal.show(video)
|
||||||
|
}
|
||||||
|
|
||||||
private removeVideoFromArray (id: number) {
|
private removeVideoFromArray (id: number) {
|
||||||
this.videos = this.videos.filter(v => v.id !== id)
|
this.videos = this.videos.filter(v => v.id !== id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private buildActions () {
|
||||||
|
this.videoActions = [
|
||||||
|
{
|
||||||
|
label: $localize`Display live information`,
|
||||||
|
handler: ({ video }) => this.displayLiveInformation(video),
|
||||||
|
isDisplayed: ({ video }) => video.isLive,
|
||||||
|
iconName: 'live'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: $localize`Change ownership`,
|
||||||
|
handler: ({ video }) => this.changeOwnership(video),
|
||||||
|
iconName: 'ownership-change'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: $localize`Delete`,
|
||||||
|
handler: ({ video }) => this.deleteVideo(video),
|
||||||
|
iconName: 'delete'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,7 +34,8 @@ import { MyAccountVideoPlaylistElementsComponent } from './my-account-video-play
|
||||||
import { MyAccountVideoPlaylistUpdateComponent } from './my-account-video-playlists/my-account-video-playlist-update.component'
|
import { MyAccountVideoPlaylistUpdateComponent } from './my-account-video-playlists/my-account-video-playlist-update.component'
|
||||||
import { MyAccountVideoPlaylistsComponent } from './my-account-video-playlists/my-account-video-playlists.component'
|
import { MyAccountVideoPlaylistsComponent } from './my-account-video-playlists/my-account-video-playlists.component'
|
||||||
import { MyAccountVideosComponent } from './my-account-videos/my-account-videos.component'
|
import { MyAccountVideosComponent } from './my-account-videos/my-account-videos.component'
|
||||||
import { VideoChangeOwnershipComponent } from './my-account-videos/video-change-ownership/video-change-ownership.component'
|
import { VideoChangeOwnershipComponent } from './my-account-videos/modals/video-change-ownership.component'
|
||||||
|
import { LiveStreamInformationComponent } from './my-account-videos/modals/live-stream-information.component'
|
||||||
import { MyAccountComponent } from './my-account.component'
|
import { MyAccountComponent } from './my-account.component'
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
@ -68,6 +69,8 @@ import { MyAccountComponent } from './my-account.component'
|
||||||
MyAccountVideosComponent,
|
MyAccountVideosComponent,
|
||||||
|
|
||||||
VideoChangeOwnershipComponent,
|
VideoChangeOwnershipComponent,
|
||||||
|
LiveStreamInformationComponent,
|
||||||
|
|
||||||
MyAccountOwnershipComponent,
|
MyAccountOwnershipComponent,
|
||||||
MyAccountAcceptOwnershipComponent,
|
MyAccountAcceptOwnershipComponent,
|
||||||
MyAccountVideoImportsComponent,
|
MyAccountVideoImportsComponent,
|
||||||
|
|
|
@ -226,7 +226,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
|
||||||
}
|
}
|
||||||
|
|
||||||
isVideoDownloadable () {
|
isVideoDownloadable () {
|
||||||
return this.video && this.video instanceof VideoDetails && this.video.downloadEnabled
|
return this.video && this.video instanceof VideoDetails && this.video.downloadEnabled && !this.video.isLive
|
||||||
}
|
}
|
||||||
|
|
||||||
loadCompleteDescription () {
|
loadCompleteDescription () {
|
||||||
|
|
|
@ -66,6 +66,7 @@ const icons = {
|
||||||
'cross': require('!!raw-loader?!../../../assets/images/feather/x.svg').default,
|
'cross': require('!!raw-loader?!../../../assets/images/feather/x.svg').default,
|
||||||
'tick': require('!!raw-loader?!../../../assets/images/feather/check.svg').default,
|
'tick': require('!!raw-loader?!../../../assets/images/feather/check.svg').default,
|
||||||
'columns': require('!!raw-loader?!../../../assets/images/feather/columns.svg').default,
|
'columns': require('!!raw-loader?!../../../assets/images/feather/columns.svg').default,
|
||||||
|
'live': require('!!raw-loader?!../../../assets/images/feather/live.svg').default,
|
||||||
'repeat': require('!!raw-loader?!../../../assets/images/feather/repeat.svg').default,
|
'repeat': require('!!raw-loader?!../../../assets/images/feather/repeat.svg').default,
|
||||||
'message-circle': require('!!raw-loader?!../../../assets/images/feather/message-circle.svg').default
|
'message-circle': require('!!raw-loader?!../../../assets/images/feather/message-circle.svg').default
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<ng-template #modal>
|
<ng-template #modal>
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h4 i18n class="modal-title">Block video "{{ video.name }}"</h4>
|
<h4 i18n class="modal-title" *ngIf="!video.isLive">Block video "{{ video.name }}"</h4>
|
||||||
|
<h4 i18n class="modal-title" *ngIf="video.isLive">Block live "{{ video.name }}"</h4>
|
||||||
<my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon>
|
<my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -28,6 +29,10 @@
|
||||||
</my-peertube-checkbox>
|
</my-peertube-checkbox>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<strong class="live-info" *ngIf="video.isLive" i18n>
|
||||||
|
Blocking this live will automatically terminate the live stream.
|
||||||
|
</strong>
|
||||||
|
|
||||||
<div class="form-group inputs">
|
<div class="form-group inputs">
|
||||||
<input
|
<input
|
||||||
type="button" role="button" i18n-value value="Cancel" class="action-button action-button-cancel"
|
type="button" role="button" i18n-value value="Cancel" class="action-button action-button-cancel"
|
||||||
|
|
|
@ -4,3 +4,8 @@
|
||||||
textarea {
|
textarea {
|
||||||
@include peertube-textarea(100%, 100px);
|
@include peertube-textarea(100%, 100px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.live-info {
|
||||||
|
font-size: 15px;
|
||||||
|
margin: 40px 0 20px 0;
|
||||||
|
}
|
||||||
|
|
|
@ -186,7 +186,12 @@ export class VideoActionsDropdownComponent implements OnChanges {
|
||||||
async removeVideo () {
|
async removeVideo () {
|
||||||
this.modalOpened.emit()
|
this.modalOpened.emit()
|
||||||
|
|
||||||
const res = await this.confirmService.confirm($localize`Do you really want to delete this video?`, $localize`Delete`)
|
let message = $localize`Do you really want to delete this video?`
|
||||||
|
if (this.video.isLive) {
|
||||||
|
message += ' ' + $localize`The live stream will be automatically terminated.`
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await this.confirmService.confirm(message, $localize`Delete`)
|
||||||
if (res === false) return
|
if (res === false) return
|
||||||
|
|
||||||
this.videoService.removeVideo(this.video.id)
|
this.videoService.removeVideo(this.video.id)
|
||||||
|
|
1
client/src/assets/images/feather/live.svg
Normal file
1
client/src/assets/images/feather/live.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-radio"><circle cx="12" cy="12" r="2"></circle><path d="M16.24 7.76a6 6 0 0 1 0 8.49m-8.48-.01a6 6 0 0 1 0-8.49m11.31-2.82a10 10 0 0 1 0 14.14m-14.14 0a10 10 0 0 1 0-14.14"></path></svg>
|
After Width: | Height: | Size: 389 B |
|
@ -1,7 +1,8 @@
|
||||||
import * as Bull from 'bull'
|
import * as Bull from 'bull'
|
||||||
import { readdir, remove } from 'fs-extra'
|
import { readdir, remove } from 'fs-extra'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
import { getVideoFileResolution, hlsPlaylistToFragmentedMP4 } from '@server/helpers/ffmpeg-utils'
|
import { getDurationFromVideoFile, getVideoFileResolution, hlsPlaylistToFragmentedMP4 } from '@server/helpers/ffmpeg-utils'
|
||||||
|
import { publishAndFederateIfNeeded } from '@server/lib/video'
|
||||||
import { getHLSDirectory } from '@server/lib/video-paths'
|
import { getHLSDirectory } from '@server/lib/video-paths'
|
||||||
import { generateHlsPlaylist } from '@server/lib/video-transcoding'
|
import { generateHlsPlaylist } from '@server/lib/video-transcoding'
|
||||||
import { VideoModel } from '@server/models/video/video'
|
import { VideoModel } from '@server/models/video/video'
|
||||||
|
@ -44,6 +45,7 @@ async function saveLive (video: MVideo, live: MVideoLive) {
|
||||||
|
|
||||||
const playlistFiles = files.filter(f => f.endsWith('.m3u8') && f !== 'master.m3u8')
|
const playlistFiles = files.filter(f => f.endsWith('.m3u8') && f !== 'master.m3u8')
|
||||||
const resolutions: number[] = []
|
const resolutions: number[] = []
|
||||||
|
let duration: number
|
||||||
|
|
||||||
for (const playlistFile of playlistFiles) {
|
for (const playlistFile of playlistFiles) {
|
||||||
const playlistPath = join(hlsDirectory, playlistFile)
|
const playlistPath = join(hlsDirectory, playlistFile)
|
||||||
|
@ -58,6 +60,10 @@ async function saveLive (video: MVideo, live: MVideoLive) {
|
||||||
const segmentFiles = files.filter(f => f.startsWith(shouldStartWith) && f.endsWith('.ts'))
|
const segmentFiles = files.filter(f => f.startsWith(shouldStartWith) && f.endsWith('.ts'))
|
||||||
await hlsPlaylistToFragmentedMP4(hlsDirectory, segmentFiles, mp4TmpName)
|
await hlsPlaylistToFragmentedMP4(hlsDirectory, segmentFiles, mp4TmpName)
|
||||||
|
|
||||||
|
if (!duration) {
|
||||||
|
duration = await getDurationFromVideoFile(mp4TmpName)
|
||||||
|
}
|
||||||
|
|
||||||
resolutions.push(videoFileResolution)
|
resolutions.push(videoFileResolution)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,6 +73,8 @@ async function saveLive (video: MVideo, live: MVideoLive) {
|
||||||
|
|
||||||
video.isLive = false
|
video.isLive = false
|
||||||
video.state = VideoState.TO_TRANSCODE
|
video.state = VideoState.TO_TRANSCODE
|
||||||
|
video.duration = duration
|
||||||
|
|
||||||
await video.save()
|
await video.save()
|
||||||
|
|
||||||
const videoWithFiles = await VideoModel.loadWithFiles(video.id)
|
const videoWithFiles = await VideoModel.loadWithFiles(video.id)
|
||||||
|
@ -86,6 +94,8 @@ async function saveLive (video: MVideo, live: MVideoLive) {
|
||||||
|
|
||||||
video.state = VideoState.PUBLISHED
|
video.state = VideoState.PUBLISHED
|
||||||
await video.save()
|
await video.save()
|
||||||
|
|
||||||
|
await publishAndFederateIfNeeded(video)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function cleanupLive (video: MVideo, streamingPlaylist: MStreamingPlaylist) {
|
async function cleanupLive (video: MVideo, streamingPlaylist: MStreamingPlaylist) {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import * as Bull from 'bull'
|
import * as Bull from 'bull'
|
||||||
|
import { publishAndFederateIfNeeded } from '@server/lib/video'
|
||||||
import { getVideoFilePath } from '@server/lib/video-paths'
|
import { getVideoFilePath } from '@server/lib/video-paths'
|
||||||
import { MVideoFullLight, MVideoUUID, MVideoWithFile } from '@server/types/models'
|
import { MVideoFullLight, MVideoUUID, MVideoWithFile } from '@server/types/models'
|
||||||
import {
|
import {
|
||||||
|
@ -174,25 +175,3 @@ function createHlsJobIfEnabled (payload?: { videoUUID: string, resolution: numbe
|
||||||
return JobQueue.Instance.createJob({ type: 'video-transcoding', payload: hlsTranscodingPayload })
|
return JobQueue.Instance.createJob({ type: 'video-transcoding', payload: hlsTranscodingPayload })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function publishAndFederateIfNeeded (video: MVideoUUID) {
|
|
||||||
const { videoDatabase, videoPublished } = await sequelizeTypescript.transaction(async t => {
|
|
||||||
// Maybe the video changed in database, refresh it
|
|
||||||
const videoDatabase = await VideoModel.loadAndPopulateAccountAndServerAndTags(video.uuid, t)
|
|
||||||
// Video does not exist anymore
|
|
||||||
if (!videoDatabase) return undefined
|
|
||||||
|
|
||||||
// We transcoded the video file in another format, now we can publish it
|
|
||||||
const videoPublished = await videoDatabase.publishIfNeededAndSave(t)
|
|
||||||
|
|
||||||
// If the video was not published, we consider it is a new one for other instances
|
|
||||||
await federateVideoIfNeeded(videoDatabase, videoPublished, t)
|
|
||||||
|
|
||||||
return { videoDatabase, videoPublished }
|
|
||||||
})
|
|
||||||
|
|
||||||
if (videoPublished) {
|
|
||||||
Notifier.Instance.notifyOnNewVideoIfNeeded(videoDatabase)
|
|
||||||
Notifier.Instance.notifyOnVideoPublishedAfterTranscoding(videoDatabase)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
import { Transaction } from 'sequelize/types'
|
import { Transaction } from 'sequelize/types'
|
||||||
|
import { sequelizeTypescript } from '@server/initializers/database'
|
||||||
import { TagModel } from '@server/models/video/tag'
|
import { TagModel } from '@server/models/video/tag'
|
||||||
import { VideoModel } from '@server/models/video/video'
|
import { VideoModel } from '@server/models/video/video'
|
||||||
import { FilteredModelAttributes } from '@server/types'
|
import { FilteredModelAttributes } from '@server/types'
|
||||||
import { MTag, MThumbnail, MVideoTag, MVideoThumbnail } from '@server/types/models'
|
import { MTag, MThumbnail, MVideoTag, MVideoThumbnail, MVideoUUID } from '@server/types/models'
|
||||||
import { ThumbnailType, VideoCreate, VideoPrivacy } from '@shared/models'
|
import { ThumbnailType, VideoCreate, VideoPrivacy } from '@shared/models'
|
||||||
|
import { federateVideoIfNeeded } from './activitypub/videos'
|
||||||
|
import { Notifier } from './notifier'
|
||||||
import { createVideoMiniatureFromExisting } from './thumbnail'
|
import { createVideoMiniatureFromExisting } from './thumbnail'
|
||||||
|
|
||||||
function buildLocalVideoFromReq (videoInfo: VideoCreate, channelId: number): FilteredModelAttributes<VideoModel> {
|
function buildLocalVideoFromReq (videoInfo: VideoCreate, channelId: number): FilteredModelAttributes<VideoModel> {
|
||||||
|
@ -78,10 +81,33 @@ async function setVideoTags (options: {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function publishAndFederateIfNeeded (video: MVideoUUID) {
|
||||||
|
const { videoDatabase, videoPublished } = await sequelizeTypescript.transaction(async t => {
|
||||||
|
// Maybe the video changed in database, refresh it
|
||||||
|
const videoDatabase = await VideoModel.loadAndPopulateAccountAndServerAndTags(video.uuid, t)
|
||||||
|
// Video does not exist anymore
|
||||||
|
if (!videoDatabase) return undefined
|
||||||
|
|
||||||
|
// We transcoded the video file in another format, now we can publish it
|
||||||
|
const videoPublished = await videoDatabase.publishIfNeededAndSave(t)
|
||||||
|
|
||||||
|
// If the video was not published, we consider it is a new one for other instances
|
||||||
|
await federateVideoIfNeeded(videoDatabase, videoPublished, t)
|
||||||
|
|
||||||
|
return { videoDatabase, videoPublished }
|
||||||
|
})
|
||||||
|
|
||||||
|
if (videoPublished) {
|
||||||
|
Notifier.Instance.notifyOnNewVideoIfNeeded(videoDatabase)
|
||||||
|
Notifier.Instance.notifyOnVideoPublishedAfterTranscoding(videoDatabase)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
export {
|
export {
|
||||||
buildLocalVideoFromReq,
|
buildLocalVideoFromReq,
|
||||||
|
publishAndFederateIfNeeded,
|
||||||
buildVideoThumbnailsFromReq,
|
buildVideoThumbnailsFromReq,
|
||||||
setVideoTags
|
setVideoTags
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue