support playlist generation, from sub selection, update packages

This commit is contained in:
jb-alvarado 2023-01-22 20:04:57 +01:00
parent 072002c684
commit 92c7a8b041
6 changed files with 236 additions and 62 deletions

1
.gitignore vendored
View File

@ -6,6 +6,7 @@ node_modules
.output
.env
dist
.eslintcache
# Output of 'npm pack'
*.tgz

84
package-lock.json generated
View File

@ -12,7 +12,7 @@
"@nuxt/types": "^2.15.8",
"@pinia/nuxt": "^0.4.6",
"@popperjs/core": "^2.11.6",
"@vueuse/core": "^9.10.0",
"@vueuse/core": "^9.11.1",
"bootstrap": "^5.3.0-alpha1",
"bootstrap-icons": "^1.10.3",
"cookie-universal-nuxt": "^2.2.2",
@ -22,9 +22,9 @@
"mpegts.js": "^1.7.2",
"pinia": "^2.0.28",
"sortablejs": "^1.15.0",
"sortablejs-vue3": "^1.2.5",
"sortablejs-vue3": "^1.2.6",
"splitpanes": "^3.1.5",
"video.js": "^7.20.3",
"video.js": "^7.21.1",
"vuedraggable": "^4.1.0"
},
"devDependencies": {
@ -33,7 +33,7 @@
"@types/lodash": "^4.14.191",
"@types/splitpanes": "^2.2.1",
"@types/video.js": "^7.3.50",
"eslint": "^8.31.0",
"eslint": "^8.32.0",
"eslint-plugin-nuxt": "^4.0.0",
"nuxt": "3.0.0",
"sass": "^1.57.1",
@ -1994,16 +1994,16 @@
}
},
"node_modules/@videojs/http-streaming": {
"version": "2.14.3",
"resolved": "https://registry.npmjs.org/@videojs/http-streaming/-/http-streaming-2.14.3.tgz",
"integrity": "sha512-2tFwxCaNbcEZzQugWf8EERwNMyNtspfHnvxRGRABQs09W/5SqmkWFuGWfUAm4wQKlXGfdPyAJ1338ASl459xAA==",
"version": "2.15.1",
"resolved": "https://registry.npmjs.org/@videojs/http-streaming/-/http-streaming-2.15.1.tgz",
"integrity": "sha512-/uuN3bVkEeJAdrhu5Hyb19JoUo3CMys7yf2C1vUjeL1wQaZ4Oe8JrZzRrnWZ0rjvPgKfNLPXQomsRtgrMoRMJQ==",
"dependencies": {
"@babel/runtime": "^7.12.5",
"@videojs/vhs-utils": "3.0.5",
"aes-decrypter": "3.1.3",
"global": "^4.4.0",
"m3u8-parser": "4.7.1",
"mpd-parser": "0.21.1",
"m3u8-parser": "4.8.0",
"mpd-parser": "^0.22.1",
"mux.js": "6.0.1",
"video.js": "^6 || ^7"
},
@ -2248,13 +2248,13 @@
"integrity": "sha512-Ewzq5Yhimg7pSztDV+RH1UDKBzmtqieXQlpTVm2AwraoRL/Rks96mvd8Vgi7Lj+h+TH8dv7mXD3FRZR3TUvbSg=="
},
"node_modules/@vueuse/core": {
"version": "9.10.0",
"resolved": "https://registry.npmjs.org/@vueuse/core/-/core-9.10.0.tgz",
"integrity": "sha512-CxMewME07qeuzuT/AOIQGv0EhhDoojniqU6pC3F8m5VC76L47UT18DcX88kWlP3I7d3qMJ4u/PD8iSRsy3bmNA==",
"version": "9.11.1",
"resolved": "https://registry.npmjs.org/@vueuse/core/-/core-9.11.1.tgz",
"integrity": "sha512-E/cizD1w9ILkq4axYjZrXLkKaBfzloaby2n3NMjUfd6yI/jkfTVgc6iwy/Cw2e++Ld4LphGbO+3MhzizvwUslQ==",
"dependencies": {
"@types/web-bluetooth": "^0.0.16",
"@vueuse/metadata": "9.10.0",
"@vueuse/shared": "9.10.0",
"@vueuse/metadata": "9.11.1",
"@vueuse/shared": "9.11.1",
"vue-demi": "*"
},
"funding": {
@ -2302,17 +2302,17 @@
}
},
"node_modules/@vueuse/metadata": {
"version": "9.10.0",
"resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-9.10.0.tgz",
"integrity": "sha512-G5VZhgTCapzU9rv0Iq2HBrVOSGzOKb+OE668NxhXNcTjUjwYxULkEhAw70FtRLMZc+hxcFAzDZlKYA0xcwNMuw==",
"version": "9.11.1",
"resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-9.11.1.tgz",
"integrity": "sha512-ABjkrG+VXggNhjfGyw5e/sekxTZfXTwjrYXkkWQmQ7Biyv+Gq9UD6IDNfeGvQZEINI0Qzw6nfuO2UFCd3hlrxQ==",
"funding": {
"url": "https://github.com/sponsors/antfu"
}
},
"node_modules/@vueuse/shared": {
"version": "9.10.0",
"resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-9.10.0.tgz",
"integrity": "sha512-vakHJ2ZRklAzqmcVBL38RS7BxdBA4+5poG9NsSyqJxrt9kz0zX3P5CXMy0Hm6LFbZXUgvKdqAS3pUH1zX/5qTQ==",
"version": "9.11.1",
"resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-9.11.1.tgz",
"integrity": "sha512-UTZYGAjT96hWn4buf4wstZbeheBVNcKPQuej6qpoSkjF1atdaeCD6kqm9uGL2waHfisSgH9mq0qCRiBOk5C/2w==",
"dependencies": {
"vue-demi": "*"
},
@ -2492,9 +2492,9 @@
}
},
"node_modules/@xmldom/xmldom": {
"version": "0.7.9",
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.7.9.tgz",
"integrity": "sha512-yceMpm/xd4W2a85iqZyO09gTnHvXF6pyiWjD2jcOJs7hRoZtNNOO1eJlhHj1ixA+xip2hOyGn+LgcvLCMo5zXA==",
"version": "0.8.6",
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.6.tgz",
"integrity": "sha512-uRjjusqpoqfmRkTaNuLJ2VohVr67Q5YwDATW3VU7PfzTj6IRaihGrYI7zckGZjxQPBIp63nfvJbM+Yu5ICh0Bg==",
"engines": {
"node": ">=10.0.0"
}
@ -4588,9 +4588,9 @@
}
},
"node_modules/eslint": {
"version": "8.31.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.31.0.tgz",
"integrity": "sha512-0tQQEVdmPZ1UtUKXjX7EMm9BlgJ08G90IhWh0PKDCb3ZLsgAOHI8fYSIzYVZej92zsgq+ft0FGsxhJ3xo2tbuA==",
"version": "8.32.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.32.0.tgz",
"integrity": "sha512-nETVXpnthqKPFyuY2FNjz/bEd6nbosRgKbkgS/y1C7LJop96gYHWpiguLecMHQ2XCPxn77DS0P+68WzG6vkZSQ==",
"dev": true,
"dependencies": {
"@eslint/eslintrc": "^1.4.1",
@ -7086,9 +7086,9 @@
}
},
"node_modules/m3u8-parser": {
"version": "4.7.1",
"resolved": "https://registry.npmjs.org/m3u8-parser/-/m3u8-parser-4.7.1.tgz",
"integrity": "sha512-pbrQwiMiq+MmI9bl7UjtPT3AK603PV9bogNlr83uC+X9IoxqL5E4k7kU7fMQ0dpRgxgeSMygqUa0IMLQNXLBNA==",
"version": "4.8.0",
"resolved": "https://registry.npmjs.org/m3u8-parser/-/m3u8-parser-4.8.0.tgz",
"integrity": "sha512-UqA2a/Pw3liR6Df3gwxrqghCP17OpPlQj6RBPLYygf/ZSQ4MoSgvdvhvt35qV+3NaaA0FSZx93Ix+2brT1U7cA==",
"dependencies": {
"@babel/runtime": "^7.12.5",
"@videojs/vhs-utils": "^3.0.5",
@ -7346,13 +7346,13 @@
}
},
"node_modules/mpd-parser": {
"version": "0.21.1",
"resolved": "https://registry.npmjs.org/mpd-parser/-/mpd-parser-0.21.1.tgz",
"integrity": "sha512-BxlSXWbKE1n7eyEPBnTEkrzhS3PdmkkKdM1pgKbPnPOH0WFZIc0sPOWi7m0Uo3Wd2a4Or8Qf4ZbS7+ASqQ49fw==",
"version": "0.22.1",
"resolved": "https://registry.npmjs.org/mpd-parser/-/mpd-parser-0.22.1.tgz",
"integrity": "sha512-fwBebvpyPUU8bOzvhX0VQZgSohncbgYwUyJJoTSNpmy7ccD2ryiCvM7oRkn/xQH5cv73/xU7rJSNCLjdGFor0Q==",
"dependencies": {
"@babel/runtime": "^7.12.5",
"@videojs/vhs-utils": "^3.0.5",
"@xmldom/xmldom": "^0.7.2",
"@xmldom/xmldom": "^0.8.3",
"global": "^4.4.0"
},
"bin": {
@ -9631,9 +9631,9 @@
"integrity": "sha512-bv9qgVMjUMf89wAvM6AxVvS/4MX3sPeN0+agqShejLU5z5GX4C75ow1O2e5k4L6XItUyAK3gH6AxSbXrOM5e8w=="
},
"node_modules/sortablejs-vue3": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/sortablejs-vue3/-/sortablejs-vue3-1.2.5.tgz",
"integrity": "sha512-aY0iDskvkRVCdKIElWjYTQOePfucQo8aiHkVEKdA7jGYw0TTRkU56ClG6lSCZi4OASVmbi+G+f5KH7ZQnI37FA==",
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/sortablejs-vue3/-/sortablejs-vue3-1.2.6.tgz",
"integrity": "sha512-VD33yCPq9FopXw80jhEn/qKPt0FSXsSe8UzOF/Y41xkClrr9DX1DA020n+frc8TsAE7LiHYBlj6ukJSFqqz8eQ==",
"dependencies": {
"sortablejs": "^1.15.0",
"vue": "^3.2.37"
@ -10460,19 +10460,19 @@
}
},
"node_modules/video.js": {
"version": "7.20.3",
"resolved": "https://registry.npmjs.org/video.js/-/video.js-7.20.3.tgz",
"integrity": "sha512-JMspxaK74LdfWcv69XWhX4rILywz/eInOVPdKefpQiZJSMD5O8xXYueqACP2Q5yqKstycgmmEKlJzZ+kVmDciw==",
"version": "7.21.1",
"resolved": "https://registry.npmjs.org/video.js/-/video.js-7.21.1.tgz",
"integrity": "sha512-AvHfr14ePDHCfW5Lx35BvXk7oIonxF6VGhSxocmTyqotkQpxwYdmt4tnQSV7MYzNrYHb0GI8tJMt20NDkCQrxg==",
"dependencies": {
"@babel/runtime": "^7.12.5",
"@videojs/http-streaming": "2.14.3",
"@videojs/http-streaming": "2.15.1",
"@videojs/vhs-utils": "^3.0.4",
"@videojs/xhr": "2.6.0",
"aes-decrypter": "3.1.3",
"global": "^4.4.0",
"keycode": "^2.2.0",
"m3u8-parser": "4.7.1",
"mpd-parser": "0.21.1",
"m3u8-parser": "4.8.0",
"mpd-parser": "0.22.1",
"mux.js": "6.0.1",
"safe-json-parse": "4.0.0",
"videojs-font": "3.2.0",

View File

@ -15,7 +15,7 @@
"@nuxt/types": "^2.15.8",
"@pinia/nuxt": "^0.4.6",
"@popperjs/core": "^2.11.6",
"@vueuse/core": "^9.10.0",
"@vueuse/core": "^9.11.1",
"bootstrap": "^5.3.0-alpha1",
"bootstrap-icons": "^1.10.3",
"cookie-universal-nuxt": "^2.2.2",
@ -25,9 +25,9 @@
"mpegts.js": "^1.7.2",
"pinia": "^2.0.28",
"sortablejs": "^1.15.0",
"sortablejs-vue3": "^1.2.5",
"sortablejs-vue3": "^1.2.6",
"splitpanes": "^3.1.5",
"video.js": "^7.20.3",
"video.js": "^7.21.1",
"vuedraggable": "^4.1.0"
},
"devDependencies": {
@ -36,7 +36,7 @@
"@types/lodash": "^4.14.191",
"@types/splitpanes": "^2.2.1",
"@types/video.js": "^7.3.50",
"eslint": "^8.31.0",
"eslint": "^8.32.0",
"eslint-plugin-nuxt": "^4.0.0",
"nuxt": "3.0.0",
"sass": "^1.57.1",

View File

@ -211,7 +211,13 @@
>
<i class="bi-file-text" />
</div>
<div class="btn btn-primary" title="Generate a randomized Playlist" @click="generatePlaylist()">
<div
class="btn btn-primary"
title="Generate a randomized Playlist"
data-bs-toggle="modal"
data-bs-target="#generateModal"
@click="mediaStore.getTree('', true)"
>
<i class="bi-sort-down-alt" />
</div>
<div class="btn btn-primary" title="Reset Playlist" @click="getPlaylist()">
@ -312,8 +318,14 @@
<button type="reset" class="btn btn-primary" data-bs-dismiss="modal" aria-label="Cancel">
Cancel
</button>
<button type="submit" class="btn btn-primary" data-bs-dismiss="modal"
@click="processSource">Ok</button>
<button
type="submit"
class="btn btn-primary"
data-bs-dismiss="modal"
@click="processSource"
>
Ok
</button>
</div>
</form>
</div>
@ -391,6 +403,115 @@
</div>
</div>
</div>
<div
id="generateModal"
class="modal modal-xl"
tabindex="-1"
aria-labelledby="generateModalLabel"
aria-hidden="true"
>
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="generateModalLabel">Generate Program</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Cancel"></button>
</div>
<div class="modal-body">
<div>
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li
class="breadcrumb-item"
v-for="(crumb, index) in mediaStore.folderCrumbs"
:key="index"
:active="index === mediaStore.folderCrumbs.length - 1"
@click="mediaStore.getTree(crumb.path, true)"
>
<a
v-if="
mediaStore.folderCrumbs.length > 1 &&
mediaStore.folderCrumbs.length - 1 > index
"
class="link-secondary"
href="#"
>
{{ crumb.text }}
</a>
<span v-else>{{ crumb.text }}</span>
</li>
</ol>
</nav>
</div>
<ul class="list-group media-browser-scroll browser-div">
<li
class="list-group-item browser-item"
v-for="folder in mediaStore.folderList.folders"
:key="folder"
>
<div class="row">
<div class="col-1 browser-icons-col">
<i class="bi-folder-fill browser-icons" />
</div>
<div class="col browser-item-text">
<a
class="link-light"
href="#"
@click="
;[
(selectedFolders = []),
mediaStore.getTree(
`/${mediaStore.folderList.source}/${folder}`.replace(
/\/[/]+/g,
'/'
),
true
),
]
"
>
{{ folder }}
</a>
</div>
<div v-if="!generateFromAll" class="col-1 text-center playlist-input">
<input
class="form-check-input"
type="checkbox"
@change="
setSelectedFolder(
$event,
`/${mediaStore.folderList.source}/${folder}`.replace(/\/[/]+/g, '/')
)
"
/>
</div>
</div>
</li>
</ul>
</div>
<div class="modal-footer">
<div class="form-check select-all-div">
<input
id="checkAll"
class="form-check-input"
type="checkbox"
v-model="generateFromAll"
/>
<label class="form-check-label" for="checkAll">All</label>
</div>
<button type="button" class="btn btn-primary" data-bs-dismiss="modal">Cancel</button>
<button
type="button"
class="btn btn-primary"
data-bs-dismiss="modal"
@click="generatePlaylist()"
>
Ok
</button>
</div>
</div>
</div>
</div>
</div>
</template>
@ -420,7 +541,7 @@ definePageMeta({
})
useHead({
title: 'Player | ffplayout'
title: 'Player | ffplayout',
})
const fileImport = ref()
@ -435,6 +556,8 @@ const previewUrl = ref('')
const previewOpt = ref()
const isVideo = ref(false)
const configID = ref(configStore.configID)
const selectedFolders = ref([])
const generateFromAll =ref(false)
const browserSortOptions = ref({
group: { name: 'playlist', pull: 'clone', put: false },
sort: false,
@ -716,10 +839,16 @@ async function onSubmitImport(evt: any) {
async function generatePlaylist() {
playlistIsLoading.value = true
await $fetch(`/api/playlist/${configStore.configGui[configStore.configID].id}/generate/${listDate.value}`, {
method: 'GET',
let payload = {
method: 'POST',
headers: authStore.authHeader,
})
}
if (selectedFolders.value.length > 0 && !generateFromAll.value) {
payload.body = { paths: selectedFolders.value }
}
await $fetch(`/api/playlist/${configStore.configGui[configStore.configID].id}/generate/${listDate.value}`, payload)
.then((response: any) => {
playlistStore.playlist = processPlaylist(
configStore.startInSec,
@ -812,6 +941,18 @@ async function deletePlaylist(playlistDate: string) {
}, 2000)
})
}
function setSelectedFolder(event: any, folder: string) {
if (event.target.checked) {
selectedFolders.value.push(folder)
} else {
const index = selectedFolders.value.indexOf(folder)
if (index > -1) {
selectedFolders.value.splice(index, 1)
}
}
}
</script>
<style lang="scss" scoped>
@ -900,9 +1041,28 @@ async function deletePlaylist(playlistDate: string) {
background-color: #ed890641 !important;
}
#generateModal .modal-body {
height: 500px;
}
#generateModal {
--bs-modal-width: 600px;
}
#generateModal .media-browser-scroll {
height: calc(100% - 35px);
}
#generateModal .browser-div li:nth-of-type(odd) {
background-color: #3b424a;
}
.select-all-div {
margin-right: 20px;
}
</style>
<style>
@media (max-width: 575px) {
@media (max-width: 575px) {
.mobile-hidden {
display: none;
}

View File

@ -9,12 +9,14 @@ export const useMedia = defineStore('media', {
state: () => ({
currentPath: '',
crumbs: [] as Crumb[],
folderTree: {} as FolderObject,
folderTree: {} as FileFolderObject,
folderList: [] as FolderObject[],
folderCrumbs: [] as Crumb[],
}),
getters: {},
actions: {
async getTree(path: string) {
async getTree(path: string, foldersOnly: boolean = false) {
const contentType = { 'content-type': 'application/json;charset=UTF-8' }
const channel = configStore.configGui[configStore.configID].id
const crumbs: Crumb[] = []
@ -23,7 +25,7 @@ export const useMedia = defineStore('media', {
await fetch(`/api/file/${channel}/browse/`, {
method: 'POST',
headers: { ...contentType, ...authStore.authHeader },
body: JSON.stringify({ source: path }),
body: JSON.stringify({ source: path, folders_only: foldersOnly }),
})
.then((response) => response.json())
.then((data) => {
@ -43,10 +45,15 @@ export const useMedia = defineStore('media', {
crumbs.push({ text: 'Home', path: '' })
}
this.currentPath = path
this.crumbs = crumbs
this.folderTree = data
if (foldersOnly) {
this.folderCrumbs = crumbs
this.folderList = data
} else {
this.currentPath = path
this.crumbs = crumbs
this.folderTree = data
}
})
},
}
},
})

View File

@ -24,13 +24,19 @@ declare global {
duration: number
}
interface FolderObject {
interface FileFolderObject {
source: string
parent: string
folders: string[]
files: FileObject[]
}
interface FolderObject {
source: string
parent: string
folders: string[]
}
interface SourceObject {
type: string
src: string