ffplayout/pages/player.vue

1043 lines
37 KiB
Vue
Raw Normal View History

2020-04-07 11:56:46 -04:00
<template>
2020-04-21 11:34:48 -04:00
<div style="height:100%;">
2020-04-15 12:10:55 -04:00
<Menu />
2020-04-07 11:56:46 -04:00
<b-container class="control-container">
<b-row class="control-row" align-v="stretch">
2020-04-21 11:34:48 -04:00
<b-col cols="3" class="player-col">
<b-aspect aspect="16:9">
<video
v-if="configGui[configID].preview_url.split('.').pop() === 'flv'"
id="httpStream"
ref="httpStream"
2022-02-06 11:49:30 -05:00
width="100%"
controls
/>
<video-player v-else-if="videoOptions.sources" :key="configID" reference="videoPlayer" :options="videoOptions" />
2020-04-07 11:56:46 -04:00
</b-aspect>
</b-col>
<b-col>
2020-04-21 11:34:48 -04:00
<b-row class="control-col">
<b-col cols="8" class="status-col">
<b-row class="status-row">
<b-col class="time-col clock-col">
2020-04-19 15:41:14 -04:00
<div class="time-str">
{{ timeStr }}
</div>
</b-col>
<b-col class="time-col counter-col">
2020-04-19 15:41:14 -04:00
<div class="time-str">
{{ timeLeft }}
</div>
</b-col>
2020-04-21 11:34:48 -04:00
<div class="w-100" />
<b-col class="current-clip" align-self="end">
<div class="current-clip-text">
{{ currentClip | filename }}
</div>
2020-05-12 06:12:18 -04:00
<div class="current-clip-meta">
<strong>Duration:</strong> {{ $secToHMS(currentClipDuration) }} | <strong>In:</strong> {{ $secToHMS(currentClipIn) }} | <strong>Out:</strong> {{ $secToHMS(currentClipOut) }}
</div>
2020-04-21 11:34:48 -04:00
<div class="current-clip-progress">
<b-progress :value="progressValue" variant="warning" />
</div>
</b-col>
2020-04-19 15:41:14 -04:00
</b-row>
2020-04-17 09:02:11 -04:00
</b-col>
2020-04-22 11:19:41 -04:00
<b-col cols="4" class="control-unit-col">
<b-row class="control-unit-row">
<b-col>
<div>
2020-04-26 14:57:40 -04:00
<b-button
title="Start Playout Service"
class="control-button control-button-play"
2020-04-27 05:57:14 -04:00
:class="isPlaying"
2020-04-26 14:57:40 -04:00
variant="primary"
@click="controlProcess('start')"
2020-04-26 14:57:40 -04:00
>
2020-04-22 11:19:41 -04:00
<b-icon-play />
</b-button>
</div>
</b-col>
<b-col>
<div>
2020-04-26 14:57:40 -04:00
<b-button
title="Stop Playout Service"
class="control-button control-button-stop"
variant="primary"
@click="controlProcess('stop')"
2020-04-26 14:57:40 -04:00
>
2020-04-22 11:19:41 -04:00
<b-icon-stop />
</b-button>
</div>
</b-col>
<b-col>
<div>
2020-04-26 14:57:40 -04:00
<b-button
title="Restart Playout Service"
class="control-button control-button-restart"
variant="primary"
@click="controlProcess('restart')"
2020-04-26 14:57:40 -04:00
>
2020-04-22 11:19:41 -04:00
<b-icon-arrow-clockwise />
</b-button>
</div>
</b-col>
<div class="w-100" />
<b-col>
<div>
<b-button
title="Jump to last Clip"
class="control-button control-button-control"
variant="primary"
@click="controlPlayout('back')"
>
<b-icon-skip-start />
</b-button>
</div>
</b-col>
<b-col>
<div>
<b-button
title="Reset Playout State"
class="control-button control-button-control"
variant="primary"
@click="controlPlayout('reset')"
>
<b-icon-arrow-repeat />
</b-button>
</div>
</b-col>
<b-col>
<div>
<b-button
title="Jump to next Clip"
class="control-button control-button-control"
variant="primary"
@click="controlPlayout('next')"
>
<b-icon-skip-end />
</b-button>
</div>
</b-col>
2020-04-22 11:19:41 -04:00
</b-row>
2020-04-17 09:02:11 -04:00
</b-col>
</b-row>
2020-04-07 11:56:46 -04:00
</b-col>
</b-row>
2020-04-17 09:02:11 -04:00
<b-row class="date-row">
2020-04-13 15:36:31 -04:00
<b-col>
2022-02-06 11:49:30 -05:00
<b-datepicker
v-if="configPlayout.playlist.path.split('.').pop() !== 'json'"
v-model="listDate"
size="sm"
class="date-div"
offset="-35px"
/>
2020-04-13 15:36:31 -04:00
</b-col>
</b-row>
2020-04-17 09:02:11 -04:00
<splitpanes class="list-row default-theme pane-row">
2020-04-08 11:25:06 -04:00
<pane min-size="20" size="24">
2020-04-16 12:30:17 -04:00
<loading
:active.sync="browserIsLoading"
2020-04-16 12:30:17 -04:00
:can-cancel="false"
:is-full-page="false"
background-color="#485159"
color="#ff9c36"
/>
<div v-if="folderTree.parent" class="browser-div">
2020-04-07 11:56:46 -04:00
<div>
<b-breadcrumb>
<b-breadcrumb-item
v-for="(crumb, index) in crumbs"
:key="crumb.key"
:active="index === crumbs.length - 1"
2022-07-06 10:22:27 -04:00
@click="getPath(crumb.path)"
2020-04-07 11:56:46 -04:00
>
{{ crumb.text }}
</b-breadcrumb-item>
</b-breadcrumb>
</div>
2021-02-05 04:23:19 -05:00
<perfect-scrollbar :options="scrollOP" class="player-browser-scroll">
2020-04-12 15:15:10 -04:00
<b-list-group>
<b-list-group-item
v-for="folder in folderTree.folders"
2020-04-12 15:15:10 -04:00
:key="folder.key"
class="browser-item"
>
2022-07-06 10:22:27 -04:00
<b-link @click="getPath(`/${folderTree.source}/${folder}`)">
2020-04-12 15:15:10 -04:00
<b-icon-folder-fill class="browser-icons" /> {{ folder }}
</b-link>
</b-list-group-item>
2020-04-24 07:58:47 -04:00
<draggable
:list="folderTree.files"
2020-04-24 07:58:47 -04:00
:clone="cloneClip"
:group="{ name: 'playlist', pull: 'clone', put: false }"
:sort="false"
2020-04-12 15:15:10 -04:00
>
2020-04-24 07:58:47 -04:00
<b-list-group-item
v-for="file in folderTree.files"
:key="file.name"
2020-04-24 07:58:47 -04:00
class="browser-item"
>
<b-row>
<b-col cols="1" class="browser-icons-col">
<b-icon-film class="browser-icons" />
</b-col>
<b-col class="browser-item-text grabbing">
{{ file.name }}
2020-04-24 07:58:47 -04:00
</b-col>
<b-col cols="1" class="browser-play-col">
<b-link @click="showPreviewModal(`/${folderTree.parent}/${folderTree.source}/${file.name}`)">
2020-04-24 07:58:47 -04:00
<b-icon-play-fill />
</b-link>
</b-col>
<b-col cols="1" class="browser-dur-col">
<span class="duration">{{ file.duration | toMin }}</span>
</b-col>
</b-row>
</b-list-group-item>
</draggable>
2020-04-12 15:15:10 -04:00
</b-list-group>
</perfect-scrollbar>
2020-04-07 11:56:46 -04:00
</div>
2020-04-08 11:25:06 -04:00
</pane>
<pane>
2020-04-12 15:15:10 -04:00
<div class="playlist-container">
<b-list-group class="list-group-header">
2020-04-12 15:15:10 -04:00
<b-list-group-item>
<b-row class="playlist-row">
2022-02-06 11:49:30 -05:00
<b-col v-if="configPlayout.playlist.day_start" cols="1" class="timecode">
2020-04-12 15:15:10 -04:00
Start
</b-col>
2020-04-24 07:58:47 -04:00
<b-col>
2020-04-12 15:15:10 -04:00
File
</b-col>
2020-04-24 07:58:47 -04:00
<b-col cols="1" class="text-center playlist-input">
2020-04-12 15:15:10 -04:00
Play
</b-col>
2020-04-22 11:19:41 -04:00
<b-col cols="1" class="timecode">
2020-04-12 15:15:10 -04:00
Duration
</b-col>
2020-04-22 11:19:41 -04:00
<b-col cols="1" class="timecode">
2020-04-12 15:15:10 -04:00
In
</b-col>
2020-04-22 11:19:41 -04:00
<b-col cols="1" class="timecode">
2020-04-12 15:15:10 -04:00
Out
</b-col>
2020-04-24 07:58:47 -04:00
<b-col cols="1" class="text-center playlist-input">
Ad
</b-col>
<b-col cols="1" class="text-center playlist-input">
2020-04-12 15:15:10 -04:00
Delete
</b-col>
</b-row>
</b-list-group-item>
</b-list-group>
<perfect-scrollbar id="scroll-container" :options="scrollOP">
<loading
:active.sync="playlistIsLoading"
:can-cancel="false"
:is-full-page="false"
background-color="#485159"
color="#ff9c36"
/>
<b-list-group class="playlist-list-group" :style="`height: ${(playlist) ? playlist.length * 52 + 52 : 300}px`">
2020-04-24 07:58:47 -04:00
<draggable
id="playlist-group"
2020-04-24 07:58:47 -04:00
v-model="playlist"
group="playlist"
@start="drag=true"
@add="scrollEnd"
2020-04-24 07:58:47 -04:00
@end="drag=false"
>
<b-list-group-item
v-for="(item, index) in playlist"
:id="`clip_${index}`"
:key="item.key"
class="playlist-item"
:class="index === currentClipIndex ? 'active-playlist-clip' : ''"
>
2020-04-24 07:58:47 -04:00
<b-row class="playlist-row">
2022-02-06 11:49:30 -05:00
<b-col v-if="configPlayout.playlist.day_start" cols="1" class="timecode">
2020-04-24 07:58:47 -04:00
{{ item.begin | secondsToTime }}
</b-col>
2021-10-31 09:59:21 -04:00
<b-col class="grabbing filename">
2020-04-24 07:58:47 -04:00
{{ item.source | filename }}
</b-col>
<b-col cols="1" class="text-center playlist-input">
<b-link @click="showPreviewModal(item.source)">
2020-04-24 07:58:47 -04:00
<b-icon-play-fill />
</b-link>
</b-col>
<b-col cols="1" text class="timecode">
{{ item.duration | secondsToTime }}
</b-col>
<b-col cols="1" class="timecode">
<b-form-input :value="item.in | secondsToTime" size="sm" @input="changeTime('in', index, $event)" />
</b-col>
<b-col cols="1" class="timecode">
<b-form-input :value="item.out | secondsToTime" size="sm" @input="changeTime('out', index, $event)" />
</b-col>
<b-col cols="1" class="text-center playlist-input">
<b-form-checkbox
v-model="item.category"
value="advertisement"
:unchecked-value="item.category"
/>
</b-col>
<b-col cols="1" class="text-center playlist-input">
<b-link @click="removeItemFromPlaylist(index)">
<b-icon-x-circle-fill />
</b-link>
</b-col>
</b-row>
</b-list-group-item>
</draggable>
2020-04-12 15:15:10 -04:00
</b-list-group>
</perfect-scrollbar>
</div>
2020-04-08 11:25:06 -04:00
</pane>
</splitpanes>
2020-04-23 11:46:43 -04:00
<b-button-group class="media-button">
<b-button v-b-tooltip.hover title="Reset Playlist" variant="primary" @click="resetPlaylist()">
<b-icon-arrow-counterclockwise />
</b-button>
<b-button v-b-tooltip.hover title="Copy Playlist" variant="primary" @click="showCopyModal()">
<b-icon-files />
</b-button>
2022-02-06 11:49:30 -05:00
<b-button v-if="!configPlayout.playlist.loop" v-b-tooltip.hover title="Loop Clips in Playlist" variant="primary" @click="loopClips()">
2021-03-23 05:43:41 -04:00
<b-icon-view-stacked />
</b-button>
2022-07-06 10:22:27 -04:00
<b-button v-b-tooltip.hover title="Add (remote) Source to Playlist" variant="primary" @click="showAddSource()">
<b-icon-file-earmark-plus />
</b-button>
<b-button v-b-tooltip.hover title="Generate a randomized Playlist" variant="primary" @click="generatePlaylist(listDate)">
<b-icon-sort-down-alt />
</b-button>
2021-03-23 05:43:41 -04:00
<b-button v-b-tooltip.hover title="Save Playlist" variant="primary" @click="savePlaylist(listDate)">
<b-icon-download />
</b-button>
2021-03-19 08:25:11 -04:00
<b-button v-b-tooltip.hover title="Delete Playlist" variant="primary" @click="showDeleteModal()">
<b-icon-trash />
</b-button>
2020-04-23 11:46:43 -04:00
</b-button-group>
2020-04-07 11:56:46 -04:00
</b-container>
2020-04-16 12:30:17 -04:00
<b-modal
id="preview-modal"
ref="prev-modal"
size="xl"
centered
2020-04-17 09:02:11 -04:00
:title="`Preview: ${previewSource}`"
2020-04-16 12:30:17 -04:00
hide-footer
>
<video-player v-if="previewOptions" reference="previewPlayer" :options="previewOptions" />
</b-modal>
<b-modal
id="copy-modal"
ref="copy-modal"
centered
:title="`Copy Program ${listDate} to:`"
content-class="copy-program"
@ok="savePlaylist(targetDate)"
>
<b-calendar v-model="targetDate" locale="en-US" class="centered" />
</b-modal>
2021-03-19 08:25:11 -04:00
<b-modal
id="delete-modal"
ref="delete-modal"
centered
title="Delete Program"
content-class="copy-program"
@ok="deletePlaylist(listDate)"
>
Delete program from {{ listDate }}
</b-modal>
2022-07-06 10:22:27 -04:00
<b-modal
id="add-source-modal"
ref="add-source-modal"
title="Add (remote) Source"
@ok="handleSource"
>
<form ref="form" @submit.stop.prevent="addSource">
<b-form-group label="In" label-for="in-input">
<b-form-input id="in-input" v-model.number="newSource.in" type="number" inline />
</b-form-group>
<b-form-group label="Out" label-for="out-input" invalid-feedback="Out is required">
<b-form-input id="out-input" v-model.number="newSource.out" type="number" inline required />
</b-form-group>
<b-form-group label="Duration" label-for="duration-input" invalid-feedback="Out is required">
<b-form-input id="duration-input" v-model.number="newSource.duration" type="number" inline required />
</b-form-group>
<b-form-group label="Source" label-for="source-input" invalid-feedback="Source is required">
<b-form-input id="source-input" v-model="newSource.source" required />
</b-form-group>
<b-form-checkbox
id="ad-input"
v-model="newSource.category"
value="advertisement"
:unchecked-value="newSource.category"
>
Advertisement
</b-form-checkbox>
</form>
</b-modal>
2020-04-07 11:56:46 -04:00
</div>
</template>
<script>
import mpegts from 'mpegts.js'
2020-12-03 11:14:02 -05:00
/* eslint-disable vue/custom-event-name-casing */
2020-04-07 11:56:46 -04:00
import { mapState } from 'vuex'
2020-04-15 12:10:55 -04:00
import Menu from '@/components/Menu.vue'
2020-04-07 11:56:46 -04:00
function scrollTo (t) {
let child
if (t.currentClipIndex === null) {
child = document.getElementById('clip_0')
} else {
child = document.getElementById(`clip_${t.currentClipIndex}`)
}
if (child) {
const parent = document.getElementById('scroll-container')
const topPos = child.offsetTop
parent.scrollTop = topPos - 50
}
}
2020-04-07 11:56:46 -04:00
export default {
2020-04-30 04:42:35 -04:00
name: 'Player',
2020-04-07 11:56:46 -04:00
2020-04-15 12:10:55 -04:00
components: {
Menu
},
2020-04-07 11:56:46 -04:00
2020-04-12 15:15:10 -04:00
filters: {
secondsToTime (sec) {
return new Date(sec * 1000).toISOString().substr(11, 8)
2020-04-12 15:15:10 -04:00
}
},
2020-12-03 11:14:02 -05:00
middleware: 'auth',
2020-04-12 15:15:10 -04:00
data () {
return {
browserIsLoading: false,
playlistIsLoading: false,
2020-04-27 05:57:14 -04:00
isPlaying: '',
2021-03-23 08:57:30 -04:00
listDate: this.$dayjs().tz(this.timezone).format('YYYY-MM-DD'),
targetDate: this.$dayjs().tz(this.timezone).format('YYYY-MM-DD'),
interval: null,
videoOptions: {
liveui: true,
controls: true,
suppressNotSupportedError: true,
autoplay: false,
preload: 'auto',
sources: []
},
httpFlvSource: {
type: 'flv',
isLive: true,
url: ''
},
mpegtsOptions: {
enableWorker: true,
lazyLoadMaxDuration: 3 * 60,
seekType: 'range',
liveBufferLatencyChasing: true
},
2020-04-16 12:30:17 -04:00
previewOptions: {},
previewComp: null,
2021-02-05 04:23:19 -05:00
previewSource: '',
scrollOP: {
suppressScrollX: true,
minScrollbarLength: 30
2022-07-06 10:22:27 -04:00
},
newSource: {
begin: 0,
in: 0,
out: 0,
duration: 0,
category: '',
source: ''
2021-02-05 04:23:19 -05:00
}
2020-04-12 15:15:10 -04:00
}
},
2020-04-07 11:56:46 -04:00
computed: {
2021-03-30 06:02:08 -04:00
...mapState('config', ['configID', 'configGui', 'configPlayout', 'timezone', 'startInSec']),
2020-04-12 15:15:10 -04:00
...mapState('media', ['crumbs', 'folderTree']),
...mapState('playlist', [
'timeStr', 'timeLeft', 'currentClip', 'progressValue', 'currentClipIndex',
2021-03-30 06:02:08 -04:00
'currentClipDuration', 'currentClipIn', 'currentClipOut']),
2020-04-24 07:58:47 -04:00
playlist: {
get () {
return this.$store.state.playlist.playlist
},
set (list) {
this.$store.commit('playlist/UPDATE_PLAYLIST', this.$processPlaylist(this.startInSec, list))
2020-04-24 07:58:47 -04:00
}
}
2020-04-12 15:15:10 -04:00
},
watch: {
listDate () {
this.playlistIsLoading = true
2020-04-12 15:15:10 -04:00
this.getPlaylist()
this.playlistIsLoading = false
setTimeout(() => { scrollTo(this) }, 5000)
2021-03-20 15:03:18 -04:00
},
configID (id) {
this.videoOptions.sources = [
{
type: 'application/x-mpegURL',
src: this.configGui[id].preview_url
}
]
2022-07-06 10:22:27 -04:00
this.getPath('')
this.getPlaylist()
setTimeout(() => { scrollTo(this) }, 3000)
2020-04-12 15:15:10 -04:00
}
2020-04-07 11:56:46 -04:00
},
2020-04-12 15:15:10 -04:00
async created () {
this.videoOptions.sources = [
{
type: 'application/x-mpegURL',
src: this.configGui[this.configID].preview_url
}
]
2020-08-27 07:38:26 -04:00
this.getStatus()
2022-07-06 10:22:27 -04:00
await this.getPath('')
2020-04-16 12:30:17 -04:00
2021-03-23 08:57:30 -04:00
const timeInSec = this.$timeToSeconds(this.$dayjs().tz(this.timezone).format('HH:mm:ss'))
const listStartSec = this.$timeToSeconds(this.configPlayout.playlist.day_start)
if (listStartSec > timeInSec) {
2021-03-23 08:57:30 -04:00
this.listDate = this.$dayjs(this.listDate).tz(this.timezone).subtract(1, 'day').format('YYYY-MM-DD')
}
2020-04-17 09:02:11 -04:00
await this.getPlaylist()
},
mounted () {
2022-07-06 10:22:27 -04:00
if (process.env.NODE_ENV === 'production') {
this.interval = setInterval(() => {
this.$store.dispatch('playlist/playoutStat')
this.getStatus()
}, 5000)
this.$store.dispatch('playlist/playoutStat')
} else {
this.$store.dispatch('playlist/playoutStat')
2022-07-06 10:22:27 -04:00
}
const streamExtension = this.configGui[this.configID].preview_url.split('.').pop()
let player
if (streamExtension === 'flv') {
this.httpFlvSource.url = this.configGui[this.configID].preview_url
const element = this.$refs.httpStream
if (typeof player !== 'undefined') {
if (player != null) {
player.unload()
player.detachMediaElement()
player.destroy()
player = null
}
}
player = mpegts.createPlayer(this.httpFlvSource, this.mpegtsOptions)
player.attachMediaElement(element)
player.load()
}
setTimeout(() => { scrollTo(this) }, 4000)
},
beforeDestroy () {
clearInterval(this.interval)
2020-04-07 11:56:46 -04:00
},
methods: {
2022-07-06 10:22:27 -04:00
async getPath (path) {
this.browserIsLoading = true
2022-07-06 10:22:27 -04:00
await this.$store.dispatch('media/getTree', { path })
this.browserIsLoading = false
2020-04-12 15:15:10 -04:00
},
2021-03-23 05:43:41 -04:00
2020-04-27 05:57:14 -04:00
async getStatus () {
const channel = this.configGui[this.configID].id
const status = await this.$axios.post(`api/control/${channel}/process/`, { command: 'status' })
2020-04-27 05:57:14 -04:00
if (status.data && status.data === 'active') {
2020-04-27 05:57:14 -04:00
this.isPlaying = 'is-playing'
} else {
this.isPlaying = ''
}
},
2021-03-23 05:43:41 -04:00
async controlProcess (state) {
const channel = this.configGui[this.configID].id
await this.$axios.post(`api/control/${channel}/process/`, { command: state })
setTimeout(() => { this.getStatus() }, 1000)
},
async controlPlayout (state) {
const channel = this.configGui[this.configID].id
await this.$axios.post(`api/control/${channel}/playout/`, { command: state })
2020-04-27 05:57:14 -04:00
2020-04-27 10:07:28 -04:00
setTimeout(() => { this.getStatus() }, 1000)
2020-04-26 14:57:40 -04:00
},
2021-03-23 05:43:41 -04:00
2020-04-12 15:15:10 -04:00
async getPlaylist () {
await this.$store.dispatch('playlist/getPlaylist', { date: this.listDate })
2020-04-16 12:30:17 -04:00
},
2021-03-23 05:43:41 -04:00
showPreviewModal (src) {
const storagePath = this.configPlayout.storage.path
const storagePathArr = storagePath.split('/')
const storageRoot = storagePathArr.pop()
src = '/' + src.substring(src.indexOf(storageRoot))
2020-04-17 09:02:11 -04:00
this.previewSource = src.split('/').slice(-1)[0]
2020-04-20 12:06:43 -04:00
const ext = this.previewSource.split('.').slice(-1)[0]
2020-04-16 12:30:17 -04:00
this.previewOptions = {
liveui: false,
controls: true,
suppressNotSupportedError: true,
autoplay: false,
preload: 'auto',
sources: [
{
2020-04-20 12:06:43 -04:00
type: `video/${ext}`,
2022-07-25 11:25:59 -04:00
src: '/' + encodeURIComponent(src.replace(/^[/]+/, '').replace(/[/]+/, '/')).replace(/%2F/g, '/')
2020-04-16 12:30:17 -04:00
}
]
}
this.$root.$emit('bv::show::modal', 'preview-modal')
2020-04-23 11:46:43 -04:00
},
2021-03-23 05:43:41 -04:00
2022-07-06 10:22:27 -04:00
cloneClip ({ name, duration }) {
const storagePath = this.configPlayout.storage.path
2022-07-06 10:22:27 -04:00
const sourcePath = `${storagePath}/${this.folderTree.source}/${name}`.replace('//', '/')
2020-04-24 07:58:47 -04:00
return {
2022-07-06 10:22:27 -04:00
source: sourcePath,
2020-04-24 07:58:47 -04:00
in: 0,
out: duration,
duration
}
},
2021-03-23 05:43:41 -04:00
scrollEnd (event) {
if (event.newIndex + 1 === this.playlist.length) {
const objDiv = document.getElementById('scroll-container')
objDiv.scrollTop = objDiv.scrollHeight
}
},
2020-04-24 07:58:47 -04:00
changeTime (pos, index, input) {
if (input.match(/(?:[01]\d|2[0123]):(?:[012345]\d):(?:[012345]\d)/gm)) {
const sec = this.$timeToSeconds(input)
if (pos === 'in') {
this.playlist[index].in = sec
} else if (pos === 'out') {
this.playlist[index].out = sec
}
this.$store.commit('playlist/UPDATE_PLAYLIST', this.$processPlaylist(this.startInSec, this.playlist))
2020-04-24 07:58:47 -04:00
}
},
2021-03-23 05:43:41 -04:00
2020-04-24 07:58:47 -04:00
removeItemFromPlaylist (index) {
this.playlist.splice(index, 1)
this.$store.commit('playlist/UPDATE_PLAYLIST', this.$processPlaylist(this.startInSec, this.playlist))
2020-04-24 07:58:47 -04:00
},
2021-03-23 05:43:41 -04:00
2020-04-24 07:58:47 -04:00
async resetPlaylist () {
await this.$store.dispatch('playlist/getPlaylist', { date: this.listDate })
2020-04-23 11:46:43 -04:00
},
2021-03-23 05:43:41 -04:00
loopClips () {
const tempList = []
let count = 0
while (count < 86400) {
for (const item of this.playlist) {
if (count < 86400) {
tempList.push(this.$_.cloneDeep(item))
count += item.out - item.in
} else {
break
}
}
}
this.$store.commit('playlist/UPDATE_PLAYLIST', this.$processPlaylist(this.startInSec, tempList))
2021-03-23 05:43:41 -04:00
},
async generatePlaylist (listDate) {
this.playlistIsLoading = true
const generate = await this.$axios.get(
`api/playlist/${this.configGui[this.configID].id}/generate/${listDate}`
)
this.playlistIsLoading = false
if (generate.status === 200 || generate.status === 201) {
this.$store.commit('UPDATE_VARIANT', 'success')
this.$store.commit('UPDATE_SHOW_ERROR_ALERT', true)
this.$store.commit('UPDATE_ERROR_ALERT_MESSAGE', 'Generate Playlist done...')
this.$store.commit('playlist/UPDATE_PLAYLIST', this.$processPlaylist(this.startInSec, generate.data.program))
setTimeout(() => { this.$store.commit('UPDATE_SHOW_ERROR_ALERT', false) }, 2000)
}
},
async savePlaylist (saveDate) {
this.$store.commit('playlist/UPDATE_PLAYLIST', this.$processPlaylist(this.startInSec, this.playlist))
2020-04-24 09:15:55 -04:00
const saveList = this.playlist.map(({ begin, ...item }) => item)
2021-10-31 10:49:21 -04:00
const postSave = await this.$axios.post(
`api/playlist/${this.configGui[this.configID].id}/`,
{ channel: this.$store.state.config.configGui.channel, date: saveDate, program: saveList }
2020-04-24 09:15:55 -04:00
)
2021-10-31 10:49:21 -04:00
if (postSave.status === 200 || postSave.status === 201) {
this.$store.commit('UPDATE_VARIANT', 'success')
this.$store.commit('UPDATE_SHOW_ERROR_ALERT', true)
this.$store.commit('UPDATE_ERROR_ALERT_MESSAGE', 'Playlist saved...')
2021-10-31 10:49:21 -04:00
setTimeout(() => { this.$store.commit('UPDATE_SHOW_ERROR_ALERT', false) }, 2000)
}
},
2021-03-23 05:43:41 -04:00
2021-03-19 08:25:11 -04:00
async deletePlaylist (playlistDate) {
const postDelete = await this.$axios.delete(`api/playlist/${this.configGui[this.configID].id}/${playlistDate}`)
2021-10-31 10:49:21 -04:00
if (postDelete.status === 200 || postDelete.status === 201) {
this.$store.commit('playlist/UPDATE_PLAYLIST', [])
2021-10-31 10:49:21 -04:00
this.$store.commit('UPDATE_VARIANT', 'success')
this.$store.commit('UPDATE_SHOW_ERROR_ALERT', true)
this.$store.commit('UPDATE_ERROR_ALERT_MESSAGE', 'Playlist deleted...')
2021-10-31 10:49:21 -04:00
setTimeout(() => { this.$store.commit('UPDATE_SHOW_ERROR_ALERT', false) }, 2000)
}
2021-03-19 08:25:11 -04:00
},
2021-03-23 05:43:41 -04:00
showCopyModal () {
this.$root.$emit('bv::show::modal', 'copy-modal')
2021-03-19 08:25:11 -04:00
},
2021-03-23 05:43:41 -04:00
2021-03-19 08:25:11 -04:00
showDeleteModal () {
this.$root.$emit('bv::show::modal', 'delete-modal')
2022-07-06 10:22:27 -04:00
},
showAddSource () {
this.$bvModal.show('add-source-modal')
},
handleSource (bvModalEvt) {
// Prevent modal from closing
bvModalEvt.preventDefault()
// Trigger submit handler
this.addSource()
},
addSource () {
const list = this.playlist
list.push(this.newSource)
this.$store.commit('playlist/UPDATE_PLAYLIST', this.$processPlaylist(this.startInSec, list))
this.newSource = {
begin: 0,
in: 0,
out: 0,
duration: 0,
category: '',
source: ''
}
this.$nextTick(() => {
this.$bvModal.hide('add-source-modal')
})
2020-04-07 11:56:46 -04:00
}
}
}
</script>
<style lang="scss" scoped>
2020-04-07 11:56:46 -04:00
.control-container {
2020-04-12 15:15:10 -04:00
width: auto;
2020-04-07 11:56:46 -04:00
max-width: 100%;
2020-04-21 11:34:48 -04:00
height: calc(100% - 40px);
2020-04-17 09:02:11 -04:00
}
.control-row {
2020-04-22 11:19:41 -04:00
min-height: 254px;
2020-04-21 11:34:48 -04:00
}
.player-col {
max-width: 542px;
min-width: 380px;
}
.control-col {
height: 100%;
2020-04-22 11:19:41 -04:00
min-height: 254px;
2020-04-21 11:34:48 -04:00
}
.status-col {
padding-right: 30px;
2020-04-22 11:19:41 -04:00
}
.control-unit-col {
2021-10-31 09:59:21 -04:00
min-width: 250px;
2020-04-22 11:19:41 -04:00
}
.control-unit-row {
background: #32383E;
height: 100%;
margin-right: 0;
border-radius: 0.25rem;
text-align: center;
}
.control-unit-row .col {
height: 50%;
min-height: 90px;
}
.control-unit-row .col div {
height: 80%;
2021-10-31 09:59:21 -04:00
margin: .6em 0;
2020-04-22 11:19:41 -04:00
}
.control-button {
font-size: 3em;
line-height: 0;
width: 80%;
height: 100%;
}
.control-button-control {
color: #06aad3;
}
.status-row {
height: 100%;
min-width: 370px;
}
.clock-col {
margin-right: 3px;
}
.counter-col {
margin-left: 3px;
}
.time-col {
position: relative;
background: #32383E;
padding: .5em;
text-align: center;
border-radius: .25rem;
}
.time-str {
position: relative;
top: 50%;
-webkit-transform: translateY(-50%);
-ms-transform: translateY(-50%);
transform: translateY(-50%);
font-family: 'DigitalNumbers-Regular';
font-size: 4.5em;
letter-spacing: -.18em;
padding-right: 14px;
}
.current-clip {
background: #32383E;
height: calc(50% - 3px);
padding: 10px;
border-radius: 0.25rem;
}
.current-clip-text {
2020-05-12 06:12:18 -04:00
height: 40%;
padding-top: .5em;
text-align: left;
font-weight: bold;
}
2020-05-12 06:12:18 -04:00
.current-clip-meta {
margin-bottom: .7em;
}
.current-clip-progress {
top: 80%;
margin-top: .2em;
}
2020-04-27 05:57:14 -04:00
.control-button:hover {
background-image: linear-gradient(#3b4046, #2c3034 60%, #24272a) !important;
}
2020-04-22 11:19:41 -04:00
.control-button-play {
color: #43c32e;
}
2020-04-27 05:57:14 -04:00
.is-playing {
box-shadow: 0 0 15px #43c32e;
}
2020-04-22 11:19:41 -04:00
.control-button-stop {
color: #d01111;
}
.control-button-reload {
color: #ed7c06;
}
.control-button-restart {
color: #f6e502;
}
@media (max-width: 1555px) {
2021-10-31 09:59:21 -04:00
.control-row {
min-height: 200px;
}
2020-04-22 11:19:41 -04:00
.control-col {
height: 100%;
2021-10-31 09:59:21 -04:00
min-height: inherit;
2020-04-22 11:19:41 -04:00
}
.status-col {
padding-right: 0;
height: 100%;
2020-04-22 11:19:41 -04:00
}
.time-str {
font-size: 3.5em;
}
.time-col {
margin-bottom: 6px;
2020-04-22 11:19:41 -04:00
}
.control-unit-row {
margin-right: -30px;
2020-04-22 11:19:41 -04:00
}
.control-unit-col {
2021-10-31 09:59:21 -04:00
flex: 0 0 28.5%;
max-width: 28.5%;
margin: 0 10px 0 20px;
}
}
@media (max-width: 1280px) {
.control-col {
min-height: inherit;
}
.control-unit-col {
flex: 0 0 25%;
max-width: 25%;
2021-10-31 10:07:59 -04:00
height: auto;
2021-10-31 09:59:21 -04:00
margin: 0 0 0 20px;
}
2020-04-22 11:19:41 -04:00
}
@media (max-width: 1225px) {
2021-10-31 09:59:21 -04:00
.control-unit-col {
flex: 0 0 66.6666666667%;
max-width: 66.6666666667%;
margin: 6px 0 0 0;
}
.clock-col {
margin-right: 0;
}
.counter-col {
margin-left: 0;
2020-04-22 11:19:41 -04:00
}
}
2020-04-17 09:02:11 -04:00
.list-row {
2020-04-23 11:46:43 -04:00
height: calc(100% - 40px - 254px - 46px - 70px);
2020-04-22 11:19:41 -04:00
min-height: 300px;
2020-04-07 11:56:46 -04:00
}
2020-04-08 11:25:06 -04:00
.pane-row {
2020-04-17 09:02:11 -04:00
margin: 0;
2020-04-08 11:25:06 -04:00
}
2020-04-12 15:15:10 -04:00
.playlist-container {
width: 100%;
2020-04-17 09:02:11 -04:00
height: 100%;
2020-04-12 15:15:10 -04:00
}
2020-04-22 11:19:41 -04:00
.timecode {
min-width: 56px;
2020-04-24 07:58:47 -04:00
max-width: 90px;
}
.playlist-input {
min-width: 35px;
max-width: 60px;
}
.timecode input {
border-color: #515763;
}
.list-group-header {
2021-03-23 05:43:41 -04:00
height: 47px;
}
.playlist-list-group, #playlist-group {
height: 100%;
}
#scroll-container {
2021-03-23 05:43:41 -04:00
height: calc(100% - 47px);
}
.playlist-item {
height: 52px;
}
2020-04-24 07:58:47 -04:00
.playlist-item:nth-of-type(even), .playlist-item:nth-of-type(even) div .timecode input {
background-color: #3b424a;
}
.playlist-item:nth-of-type(even):hover {
background-color: #1C1E22;
2020-04-22 11:19:41 -04:00
}
.clip-progress {
height: 5px;
padding-top: 3px;
}
.active-playlist-clip {
background-color: #565e6a !important;
}
2021-10-31 10:14:20 -04:00
.filename, .browser-item {
2021-10-31 09:59:21 -04:00
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
2020-04-07 11:56:46 -04:00
</style>
<style>
.copy-program {
width: 302px !important;
}
</style>