forked from platypush/platypush
[UI] Many improvements for the media
UI.
- Support for _Play_ / _Play (With Cache)_ options for YouTube videos. - Added `media.chromecast` and `media.gstreamer` UI panels. - Removed `media.omxplayer` - the plugin has been removed. - Enriched and improved the media info component. - Propagate the media loading state to all children components. - Persist query/search state on the URL. Closes: #422
This commit is contained in:
parent
a21aaee888
commit
01571e2e65
24 changed files with 307 additions and 105 deletions
|
@ -53,15 +53,15 @@
|
||||||
"linode": {
|
"linode": {
|
||||||
"class": "fas fa-cloud"
|
"class": "fas fa-cloud"
|
||||||
},
|
},
|
||||||
|
"media.chromecast": {
|
||||||
|
"class": "fab fa-chromecast"
|
||||||
|
},
|
||||||
"media.jellyfin": {
|
"media.jellyfin": {
|
||||||
"imgUrl": "/icons/jellyfin.svg"
|
"imgUrl": "/icons/jellyfin.svg"
|
||||||
},
|
},
|
||||||
"media.kodi": {
|
"media.kodi": {
|
||||||
"imgUrl": "/icons/kodi.svg"
|
"imgUrl": "/icons/kodi.svg"
|
||||||
},
|
},
|
||||||
"media.omxplayer": {
|
|
||||||
"class": "fa fa-film"
|
|
||||||
},
|
|
||||||
"media.mplayer": {
|
"media.mplayer": {
|
||||||
"class": "fa fa-film"
|
"class": "fa fa-film"
|
||||||
},
|
},
|
||||||
|
|
|
@ -57,7 +57,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="track-container col-s-9 col-m-9 col-l-3">
|
<div class="track-container col-s-9 col-m-9 col-l-3">
|
||||||
<div class="track-info" v-if="track && status?.state !== 'stop'">
|
<div class="track-info" @click="$emit('info', track)" v-if="track && status?.state !== 'stop'">
|
||||||
<div class="img-container" v-if="trackImage">
|
<div class="img-container" v-if="trackImage">
|
||||||
<img class="image from desktop" :src="trackImage" :alt="track.title">
|
<img class="image from desktop" :src="trackImage" :alt="track.title">
|
||||||
</div>
|
</div>
|
||||||
|
@ -127,6 +127,7 @@ export default {
|
||||||
mixins: [Utils, MediaUtils],
|
mixins: [Utils, MediaUtils],
|
||||||
emits: [
|
emits: [
|
||||||
'consume',
|
'consume',
|
||||||
|
'info',
|
||||||
'mute',
|
'mute',
|
||||||
'next',
|
'next',
|
||||||
'pause',
|
'pause',
|
||||||
|
@ -202,6 +203,9 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
trackImage() {
|
trackImage() {
|
||||||
|
if (this.track?.images?.length)
|
||||||
|
return this.track.images[0].url
|
||||||
|
|
||||||
return this.track?.image || this.image
|
return this.track?.image || this.image
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -405,6 +409,10 @@ button {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
|
button {
|
||||||
|
background: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
.row {
|
.row {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -466,8 +474,14 @@ button {
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
|
.img-container {
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: calc(100% + 3em);
|
||||||
|
}
|
||||||
|
|
||||||
.image {
|
.image {
|
||||||
margin-right: 0.75em;
|
padding: 0.5em;
|
||||||
|
max-height: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
:status="status"
|
:status="status"
|
||||||
:track="track"
|
:track="track"
|
||||||
@consume="$emit('consume', $event)"
|
@consume="$emit('consume', $event)"
|
||||||
|
@info="$emit('info', $event)"
|
||||||
@mute="$emit('mute')"
|
@mute="$emit('mute')"
|
||||||
@next="$emit('next')"
|
@next="$emit('next')"
|
||||||
@pause="$emit('pause', $event)"
|
@pause="$emit('pause', $event)"
|
||||||
|
@ -33,6 +34,7 @@ export default {
|
||||||
components: {Controls},
|
components: {Controls},
|
||||||
emits: [
|
emits: [
|
||||||
'consume',
|
'consume',
|
||||||
|
'info',
|
||||||
'mute',
|
'mute',
|
||||||
'next',
|
'next',
|
||||||
'pause',
|
'pause',
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<keep-alive>
|
<keep-alive>
|
||||||
<div class="media-browser">
|
<div class="media-browser">
|
||||||
<Loading v-if="loading" />
|
<div class="media-index grid" v-if="!mediaProvider">
|
||||||
|
|
||||||
<div class="media-index grid" v-else-if="!mediaProvider">
|
|
||||||
<div class="item"
|
<div class="item"
|
||||||
v-for="(provider, name) in mediaProviders"
|
v-for="(provider, name) in mediaProviders"
|
||||||
:key="name"
|
:key="name"
|
||||||
|
@ -19,10 +17,11 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="media-browser-body" v-else-if="mediaProvider">
|
<div class="media-browser-body" v-if="mediaProvider">
|
||||||
<component
|
<component
|
||||||
:is="mediaProvider"
|
:is="mediaProvider"
|
||||||
:filter="filter"
|
:filter="filter"
|
||||||
|
:loading="loading"
|
||||||
:selected-playlist="selectedPlaylist"
|
:selected-playlist="selectedPlaylist"
|
||||||
:selected-channel="selectedChannel"
|
:selected-channel="selectedChannel"
|
||||||
@add-to-playlist="$emit('add-to-playlist', $event)"
|
@add-to-playlist="$emit('add-to-playlist', $event)"
|
||||||
|
@ -31,6 +30,7 @@
|
||||||
@download-audio="$emit('download-audio', $event)"
|
@download-audio="$emit('download-audio', $event)"
|
||||||
@path-change="$emit('path-change', $event)"
|
@path-change="$emit('path-change', $event)"
|
||||||
@play="$emit('play', $event)"
|
@play="$emit('play', $event)"
|
||||||
|
@play-cache="$emit('play-cache', $event)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -40,7 +40,6 @@
|
||||||
<script>
|
<script>
|
||||||
import { defineAsyncComponent, ref } from "vue";
|
import { defineAsyncComponent, ref } from "vue";
|
||||||
import Browser from "@/components/File/Browser";
|
import Browser from "@/components/File/Browser";
|
||||||
import Loading from "@/components/Loading";
|
|
||||||
import Utils from "@/Utils";
|
import Utils from "@/Utils";
|
||||||
import providersMetadata from "./Providers/meta.json";
|
import providersMetadata from "./Providers/meta.json";
|
||||||
|
|
||||||
|
@ -54,6 +53,7 @@ export default {
|
||||||
'download-audio',
|
'download-audio',
|
||||||
'path-change',
|
'path-change',
|
||||||
'play',
|
'play',
|
||||||
|
'play-cache',
|
||||||
'remove-from-playlist',
|
'remove-from-playlist',
|
||||||
'remove-playlist',
|
'remove-playlist',
|
||||||
'rename-playlist',
|
'rename-playlist',
|
||||||
|
@ -61,7 +61,6 @@ export default {
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
Browser,
|
Browser,
|
||||||
Loading,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
|
@ -77,11 +76,15 @@ export default {
|
||||||
selectedChannel: {
|
selectedChannel: {
|
||||||
type: Object,
|
type: Object,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
loading: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
loading: false,
|
|
||||||
mediaProvider: null,
|
mediaProvider: null,
|
||||||
mediaProviders: {},
|
mediaProviders: {},
|
||||||
providersMetadata: providersMetadata,
|
providersMetadata: providersMetadata,
|
||||||
|
|
|
@ -67,10 +67,13 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Players from "@/components/panels/Media/Players";
|
import Players from "@/components/panels/Media/Players"
|
||||||
|
import Utils from '@/Utils'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "Header",
|
name: "Header",
|
||||||
components: {Players},
|
components: {Players},
|
||||||
|
mixins: [Utils],
|
||||||
emits: [
|
emits: [
|
||||||
'filter',
|
'filter',
|
||||||
'filter-downloads',
|
'filter-downloads',
|
||||||
|
@ -132,20 +135,36 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
enabledTypes() {
|
||||||
|
return Object.keys(this.sources).filter((source) => this.sources[source])
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
search() {
|
search() {
|
||||||
const types = Object.keys(this.sources).filter((source) => this.sources[source])
|
if (!this.query?.length || !this.enabledTypes?.length)
|
||||||
if (!this.query?.length || !types?.length)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
this.$emit('search', {
|
this.$emit('search', {
|
||||||
query: this.query,
|
query: this.query,
|
||||||
types: types,
|
types: this.enabledTypes,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
const query = this.getUrlArgs()?.q
|
||||||
|
if (query) {
|
||||||
|
this.query = query
|
||||||
|
this.$emit('search', {
|
||||||
|
query: query,
|
||||||
|
types: this.enabledTypes,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
this.$watch(() => this.selectedView, () => {
|
this.$watch(() => this.selectedView, () => {
|
||||||
this.$emit('filter', '')
|
this.$emit('filter', '')
|
||||||
this.torrentURL = ''
|
this.torrentURL = ''
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
:status="selectedPlayer?.status || {}"
|
:status="selectedPlayer?.status || {}"
|
||||||
:track="selectedPlayer?.status || {}"
|
:track="selectedPlayer?.status || {}"
|
||||||
:buttons="mediaButtons"
|
:buttons="mediaButtons"
|
||||||
|
@info="infoTrack = $event"
|
||||||
@play="pause"
|
@play="pause"
|
||||||
@pause="pause"
|
@pause="pause"
|
||||||
@stop="stop"
|
@stop="stop"
|
||||||
|
@ -58,6 +59,7 @@
|
||||||
@open-channel="selectChannelFromItem"
|
@open-channel="selectChannelFromItem"
|
||||||
@select="onResultSelect($event)"
|
@select="onResultSelect($event)"
|
||||||
@play="play"
|
@play="play"
|
||||||
|
@play-cache="play($event, {cache: true})"
|
||||||
@view="view"
|
@view="view"
|
||||||
@download="download"
|
@download="download"
|
||||||
@download-audio="downloadAudio"
|
@download-audio="downloadAudio"
|
||||||
|
@ -78,6 +80,7 @@
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Browser :filter="browserFilter"
|
<Browser :filter="browserFilter"
|
||||||
|
:loading="loading"
|
||||||
:selected-playlist="selectedPlaylist"
|
:selected-playlist="selectedPlaylist"
|
||||||
:selected-channel="selectedChannel"
|
:selected-channel="selectedChannel"
|
||||||
@add-to-playlist="addToPlaylistItem = $event"
|
@add-to-playlist="addToPlaylistItem = $event"
|
||||||
|
@ -86,6 +89,7 @@
|
||||||
@download-audio="downloadAudio"
|
@download-audio="downloadAudio"
|
||||||
@path-change="browserFilter = ''"
|
@path-change="browserFilter = ''"
|
||||||
@play="play($event)"
|
@play="play($event)"
|
||||||
|
@play-cache="play($event, {cache: true})"
|
||||||
v-else-if="selectedView === 'browser'"
|
v-else-if="selectedView === 'browser'"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -119,6 +123,20 @@
|
||||||
/>
|
/>
|
||||||
</Modal>
|
</Modal>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="info-container" v-if="infoTrack != null">
|
||||||
|
<Modal ref="infoModal" title="Media info" :visible="infoTrack != null" @close="infoTrack = null">
|
||||||
|
<Info :item="infoTrack"
|
||||||
|
:pluginName="pluginName"
|
||||||
|
@add-to-playlist="addToPlaylistItem = $event"
|
||||||
|
@download="download"
|
||||||
|
@download-audio="downloadAudio"
|
||||||
|
@open-channel="selectChannelFromItem"
|
||||||
|
@play="play"
|
||||||
|
@play-cache="play($event, {cache: true})"
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</keep-alive>
|
</keep-alive>
|
||||||
</template>
|
</template>
|
||||||
|
@ -129,6 +147,7 @@ import Utils from "@/Utils";
|
||||||
|
|
||||||
import Browser from "@/components/panels/Media/Browser";
|
import Browser from "@/components/panels/Media/Browser";
|
||||||
import Header from "@/components/panels/Media/Header";
|
import Header from "@/components/panels/Media/Header";
|
||||||
|
import Info from "@/components/panels/Media/Info";
|
||||||
import MediaDownloads from "@/components/panels/Media/Downloads";
|
import MediaDownloads from "@/components/panels/Media/Downloads";
|
||||||
import MediaUtils from "@/components/Media/Utils";
|
import MediaUtils from "@/components/Media/Utils";
|
||||||
import MediaView from "@/components/Media/View";
|
import MediaView from "@/components/Media/View";
|
||||||
|
@ -145,6 +164,7 @@ export default {
|
||||||
components: {
|
components: {
|
||||||
Browser,
|
Browser,
|
||||||
Header,
|
Header,
|
||||||
|
Info,
|
||||||
MediaDownloads,
|
MediaDownloads,
|
||||||
MediaView,
|
MediaView,
|
||||||
Modal,
|
Modal,
|
||||||
|
@ -176,33 +196,32 @@ export default {
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
loading: false,
|
|
||||||
results: [],
|
|
||||||
selectedResult: null,
|
|
||||||
selectedPlayer: null,
|
|
||||||
selectedView: 'search',
|
|
||||||
selectedSubtitles: null,
|
|
||||||
prevSelectedView: null,
|
|
||||||
showSubtitlesModal: false,
|
|
||||||
forceShowNav: false,
|
|
||||||
awaitingPlayTorrent: null,
|
|
||||||
urlPlay: null,
|
|
||||||
browserFilter: null,
|
|
||||||
downloadsFilter: null,
|
|
||||||
addToPlaylistItem: null,
|
addToPlaylistItem: null,
|
||||||
torrentPlugin: null,
|
awaitingPlayTorrent: null,
|
||||||
torrentPlugins: [
|
browserFilter: null,
|
||||||
'torrent',
|
downloads: {},
|
||||||
'rtorrent',
|
downloadsFilter: null,
|
||||||
],
|
forceShowNav: false,
|
||||||
|
infoTrack: null,
|
||||||
|
loading: false,
|
||||||
|
prevSelectedView: null,
|
||||||
|
results: [],
|
||||||
|
selectedPlayer: null,
|
||||||
|
selectedResult: null,
|
||||||
|
selectedSubtitles: null,
|
||||||
|
selectedView: 'search',
|
||||||
|
showSubtitlesModal: false,
|
||||||
sources: {
|
sources: {
|
||||||
'file': true,
|
'file': true,
|
||||||
'youtube': true,
|
'youtube': true,
|
||||||
'torrent': true,
|
'torrent': true,
|
||||||
},
|
},
|
||||||
|
urlPlay: null,
|
||||||
downloads: {},
|
torrentPlugin: null,
|
||||||
|
torrentPlugins: [
|
||||||
|
'torrent',
|
||||||
|
'rtorrent',
|
||||||
|
],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -281,6 +300,7 @@ export default {
|
||||||
methods: {
|
methods: {
|
||||||
async search(event) {
|
async search(event) {
|
||||||
this.loading = true
|
this.loading = true
|
||||||
|
this.setUrlArgs({q: event.query})
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.results = await this.request(`${this.pluginName}.search`, event)
|
this.results = await this.request(`${this.pluginName}.search`, event)
|
||||||
|
@ -289,7 +309,7 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async play(item) {
|
async play(item, opts) {
|
||||||
if (item?.type === 'torrent') {
|
if (item?.type === 'torrent') {
|
||||||
this.awaitingPlayTorrent = item.url
|
this.awaitingPlayTorrent = item.url
|
||||||
this.notify({
|
this.notify({
|
||||||
|
@ -309,7 +329,10 @@ export default {
|
||||||
if (!this.selectedPlayer.component.supports(item))
|
if (!this.selectedPlayer.component.supports(item))
|
||||||
item = await this.startStreaming(item, this.pluginName)
|
item = await this.startStreaming(item, this.pluginName)
|
||||||
|
|
||||||
await this.selectedPlayer.component.play(item, this.selectedSubtitles, this.selectedPlayer)
|
await this.selectedPlayer.component.play(
|
||||||
|
item, this.selectedSubtitles, this.selectedPlayer, opts
|
||||||
|
)
|
||||||
|
|
||||||
await this.refresh()
|
await this.refresh()
|
||||||
} finally {
|
} finally {
|
||||||
this.loading = false
|
this.loading = false
|
||||||
|
|
|
@ -1,28 +1,25 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="media-info">
|
<div class="media-info">
|
||||||
<div class="row header">
|
<div class="row header">
|
||||||
<div class="image-container">
|
<div class="item-container">
|
||||||
<MediaImage :item="item" @play="$emit('play')" @select="$emit('select')" />
|
<Item :item="item"
|
||||||
</div>
|
@add-to-playlist="$emit('add-to-playlist', item)"
|
||||||
|
@open-channel="$emit('open-channel', item)"
|
||||||
<div class="title">
|
@play="$emit('play', item)"
|
||||||
<i :class="typeIcons[item.type]"
|
@play-cache="$emit('play-cache', item)"
|
||||||
:title="item.type"
|
@download="$emit('download', item)"
|
||||||
v-if="typeIcons[item?.type]">
|
@download-audio="$emit('download-audio', item)"
|
||||||
|
/>
|
||||||
</i>
|
|
||||||
<a :href="item.url" target="_blank" v-if="item.url" v-text="item.title" />
|
|
||||||
<span v-else v-text="item.title" />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row direct-url" v-if="directUrl">
|
<div class="row direct-url" v-if="mainUrl">
|
||||||
<div class="left side">Direct URL</div>
|
<div class="left side">Direct URL</div>
|
||||||
<div class="right side">
|
<div class="right side">
|
||||||
<a :href="directUrl" title="Direct URL" target="_blank">
|
<a :href="mainUrl" title="Direct URL" target="_blank">
|
||||||
<i class="fas fa-external-link-alt" />
|
<i class="fas fa-external-link-alt" />
|
||||||
</a>
|
</a>
|
||||||
<button @click="copyToClipboard(directUrl)" title="Copy URL to clipboard">
|
<button @click="copyToClipboard(mainUrl)" title="Copy URL to clipboard">
|
||||||
<i class="fas fa-clipboard" />
|
<i class="fas fa-clipboard" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -85,6 +82,11 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="row" v-if="item?.view_count != null">
|
||||||
|
<div class="left side">Views</div>
|
||||||
|
<div class="right side">{{ formatNumber(item.view_count) }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="row" v-if="item?.rating">
|
<div class="row" v-if="item?.rating">
|
||||||
<div class="left side">Rating</div>
|
<div class="left side">Rating</div>
|
||||||
<div class="right side">{{ item.rating }}%</div>
|
<div class="right side">{{ item.rating }}%</div>
|
||||||
|
@ -163,20 +165,34 @@
|
||||||
<div class="left side">Language</div>
|
<div class="left side">Language</div>
|
||||||
<div class="right side" v-text="item.language" />
|
<div class="right side" v-text="item.language" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="row" v-if="item?.audio_channels">
|
||||||
|
<div class="left side">Audio Channels</div>
|
||||||
|
<div class="right side" v-text="item.audio_channels" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Utils from "@/Utils";
|
|
||||||
import MediaUtils from "@/components/Media/Utils";
|
|
||||||
import MediaImage from "./MediaImage";
|
|
||||||
import Icons from "./icons.json";
|
import Icons from "./icons.json";
|
||||||
|
import Item from "./Item";
|
||||||
|
import MediaUtils from "@/components/Media/Utils";
|
||||||
|
import Utils from "@/Utils";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "Info",
|
name: "Info",
|
||||||
components: {MediaImage},
|
components: {
|
||||||
|
Item,
|
||||||
|
},
|
||||||
mixins: [Utils, MediaUtils],
|
mixins: [Utils, MediaUtils],
|
||||||
emits: ['play'],
|
emits: [
|
||||||
|
'add-to-playlist',
|
||||||
|
'download',
|
||||||
|
'download-audio',
|
||||||
|
'open-channel',
|
||||||
|
'play',
|
||||||
|
'play-cache',
|
||||||
|
],
|
||||||
props: {
|
props: {
|
||||||
item: {
|
item: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
@ -224,6 +240,8 @@ export default {
|
||||||
return this.formatDate(this.item.publishedAt, true)
|
return this.formatDate(this.item.publishedAt, true)
|
||||||
if (this.item?.created_at)
|
if (this.item?.created_at)
|
||||||
return this.formatDate(this.item.created_at, true)
|
return this.formatDate(this.item.created_at, true)
|
||||||
|
if (this.item?.timestamp)
|
||||||
|
return this.formatDate(this.item.timestamp, true)
|
||||||
|
|
||||||
return null
|
return null
|
||||||
},
|
},
|
||||||
|
@ -236,6 +254,14 @@ export default {
|
||||||
|
|
||||||
return null
|
return null
|
||||||
},
|
},
|
||||||
|
|
||||||
|
mainUrl() {
|
||||||
|
const directUrl = this.directUrl
|
||||||
|
if (directUrl)
|
||||||
|
return directUrl
|
||||||
|
|
||||||
|
return this.item?.url
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -331,11 +357,9 @@ export default {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
.image-container {
|
.item-container {
|
||||||
@include from($desktop) {
|
@include from($desktop) {
|
||||||
.image-container {
|
width: 420px;
|
||||||
width: 420px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,8 @@
|
||||||
<Dropdown title="Actions" icon-class="fa fa-ellipsis-h" ref="dropdown">
|
<Dropdown title="Actions" icon-class="fa fa-ellipsis-h" ref="dropdown">
|
||||||
<DropdownItem icon-class="fa fa-play" text="Play" @input="$emit('play')"
|
<DropdownItem icon-class="fa fa-play" text="Play" @input="$emit('play')"
|
||||||
v-if="item.type !== 'torrent'" />
|
v-if="item.type !== 'torrent'" />
|
||||||
|
<DropdownItem icon-class="fa fa-play" text="Play (With Cache)" @input="$emit('play-cache')"
|
||||||
|
v-if="item.type === 'youtube'" />
|
||||||
<DropdownItem icon-class="fa fa-download" text="Download" @input="$emit('download')"
|
<DropdownItem icon-class="fa fa-download" text="Download" @input="$emit('download')"
|
||||||
v-if="(item.type === 'torrent' || item.type === 'youtube') && item.item_type !== 'channel' && item.item_type !== 'playlist'" />
|
v-if="(item.type === 'torrent' || item.type === 'youtube') && item.item_type !== 'channel' && item.item_type !== 'playlist'" />
|
||||||
<DropdownItem icon-class="fa fa-volume-high" text="Download Audio" @input="$emit('download-audio')"
|
<DropdownItem icon-class="fa fa-volume-high" text="Download Audio" @input="$emit('download-audio')"
|
||||||
|
@ -60,6 +62,7 @@ export default {
|
||||||
'download-audio',
|
'download-audio',
|
||||||
'open-channel',
|
'open-channel',
|
||||||
'play',
|
'play',
|
||||||
|
'play-cache',
|
||||||
'remove-from-playlist',
|
'remove-from-playlist',
|
||||||
'select',
|
'select',
|
||||||
'view',
|
'view',
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<img class="image" :src="item.image" :alt="item.title" v-if="item?.image" />
|
<img class="image" :src="imgUrl" :alt="item.title" v-if="imgUrl" />
|
||||||
<div class="image" v-else>
|
<div class="image" v-else>
|
||||||
<div class="inner">
|
<div class="inner">
|
||||||
<i :class="iconClass" />
|
<i :class="iconClass" />
|
||||||
|
@ -84,6 +84,15 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
imgUrl() {
|
||||||
|
let img = this.item?.image
|
||||||
|
if (!img) {
|
||||||
|
img = this.item?.images?.[0]?.url
|
||||||
|
}
|
||||||
|
|
||||||
|
return img
|
||||||
|
},
|
||||||
|
|
||||||
overlayIconClass() {
|
overlayIconClass() {
|
||||||
if (
|
if (
|
||||||
this.item?.item_type === 'channel' ||
|
this.item?.item_type === 'channel' ||
|
||||||
|
|
|
@ -9,8 +9,8 @@
|
||||||
@status="$emit('status', $event)" />
|
@status="$emit('status', $event)" />
|
||||||
<Mpv :player="selectedPlayer?.pluginName === 'media.mpv' ? selectedPlayer : null" ref="mpvPlugin"
|
<Mpv :player="selectedPlayer?.pluginName === 'media.mpv' ? selectedPlayer : null" ref="mpvPlugin"
|
||||||
@status="$emit('status', $event)" />
|
@status="$emit('status', $event)" />
|
||||||
<Omxplayer :player="selectedPlayer?.pluginName === 'media.omxplayer' ? selectedPlayer : null" ref="omxplayerPlugin"
|
<GStreamer :player="selectedPlayer?.pluginName === 'media.gstreamer' ? selectedPlayer : null"
|
||||||
@status="$emit('status', $event)" />
|
ref="gstreamerPlugin" @status="$emit('status', $event)" />
|
||||||
<Vlc :player="selectedPlayer?.pluginName === 'media.vlc' ? selectedPlayer : null" ref="vlcPlugin"
|
<Vlc :player="selectedPlayer?.pluginName === 'media.vlc' ? selectedPlayer : null" ref="vlcPlugin"
|
||||||
@status="$emit('status', $event)" />
|
@status="$emit('status', $event)" />
|
||||||
</div>
|
</div>
|
||||||
|
@ -40,18 +40,20 @@
|
||||||
import Dropdown from "@/components/elements/Dropdown";
|
import Dropdown from "@/components/elements/Dropdown";
|
||||||
import DropdownItem from "@/components/elements/DropdownItem";
|
import DropdownItem from "@/components/elements/DropdownItem";
|
||||||
import Loading from "@/components/Loading";
|
import Loading from "@/components/Loading";
|
||||||
|
import Utils from '@/Utils'
|
||||||
|
|
||||||
import Chromecast from "@/components/panels/Media/Players/Chromecast"
|
import Chromecast from "@/components/panels/Media/Players/Chromecast"
|
||||||
import Kodi from "@/components/panels/Media/Players/Kodi";
|
import Kodi from "@/components/panels/Media/Players/Kodi";
|
||||||
import Mplayer from "@/components/panels/Media/Players/Mplayer";
|
import Mplayer from "@/components/panels/Media/Players/Mplayer";
|
||||||
import Mpv from "@/components/panels/Media/Players/Mpv";
|
import Mpv from "@/components/panels/Media/Players/Mpv";
|
||||||
import Omxplayer from "@/components/panels/Media/Players/Omxplayer";
|
import GStreamer from "@/components/panels/Media/Players/GStreamer";
|
||||||
import Vlc from "@/components/panels/Media/Players/Vlc";
|
import Vlc from "@/components/panels/Media/Players/Vlc";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "Players",
|
name: "Players",
|
||||||
components: {Loading, DropdownItem, Dropdown, Chromecast, Kodi, Mplayer, Mpv, Omxplayer, Vlc},
|
components: {Loading, DropdownItem, Dropdown, Chromecast, Kodi, Mplayer, Mpv, GStreamer, Vlc},
|
||||||
emits: ['select', 'status'],
|
emits: ['select', 'status'],
|
||||||
|
mixins: [Utils],
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
pluginName: {
|
pluginName: {
|
||||||
|
@ -89,7 +91,16 @@ export default {
|
||||||
this.players.push(...players)
|
this.players.push(...players)
|
||||||
|
|
||||||
if (this.selectedPlayer == null && plugin.pluginName === this.pluginName && players.length > 0) {
|
if (this.selectedPlayer == null && plugin.pluginName === this.pluginName && players.length > 0) {
|
||||||
this.select(players[0])
|
const urlSelectedPlayer = this.getUrlArgs().player
|
||||||
|
let player = players[0]
|
||||||
|
|
||||||
|
if (urlSelectedPlayer?.length) {
|
||||||
|
player = players.find((p) => p.name === urlSelectedPlayer)
|
||||||
|
if (!player)
|
||||||
|
player = players[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
this.select(player)
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
} finally {
|
} finally {
|
||||||
|
|
|
@ -8,6 +8,7 @@ import Mixin from "@/components/panels/Media/Players/Mixin";
|
||||||
export default {
|
export default {
|
||||||
name: "Chromecast",
|
name: "Chromecast",
|
||||||
mixins: [Mixin],
|
mixins: [Mixin],
|
||||||
|
emits: ['status'],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
name: 'Chromecast',
|
name: 'Chromecast',
|
||||||
|
@ -48,12 +49,20 @@ export default {
|
||||||
)?.status
|
)?.status
|
||||||
},
|
},
|
||||||
|
|
||||||
async play(resource, player) {
|
async play(resource, subs, player) {
|
||||||
if (!resource) {
|
if (!resource) {
|
||||||
return await this.pause(player)
|
return await this.pause(player)
|
||||||
}
|
}
|
||||||
|
|
||||||
return await this.request(`${this.pluginName}.play`, {resource: resource.url, chromecast: this.getPlayerName(player)})
|
return await this.request(
|
||||||
|
`${this.pluginName}.play`,
|
||||||
|
{
|
||||||
|
resource: resource.url,
|
||||||
|
chromecast: this.getPlayerName(player),
|
||||||
|
subtitles: subs,
|
||||||
|
metadata: resource,
|
||||||
|
}
|
||||||
|
)
|
||||||
},
|
},
|
||||||
|
|
||||||
async pause(player) {
|
async pause(player) {
|
||||||
|
|
|
@ -6,13 +6,12 @@
|
||||||
import Mixin from "@/components/panels/Media/Players/Mixin";
|
import Mixin from "@/components/panels/Media/Players/Mixin";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "Omxplayer",
|
|
||||||
mixins: [Mixin],
|
mixins: [Mixin],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
iconClass: 'fa fa-tv',
|
iconClass: 'fa fa-tv',
|
||||||
name: 'OMXPlayer',
|
name: 'GStreamer',
|
||||||
pluginName: 'media.omxplayer',
|
pluginName: 'media.gstreamer',
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
|
@ -36,12 +36,17 @@ export default {
|
||||||
return await this.request(`${this.pluginName}.status`)
|
return await this.request(`${this.pluginName}.status`)
|
||||||
},
|
},
|
||||||
|
|
||||||
async play(resource, subs) {
|
async play(resource, subs, _, opts) {
|
||||||
if (!resource) {
|
if (!resource) {
|
||||||
return await this.pause()
|
return await this.pause()
|
||||||
}
|
}
|
||||||
|
|
||||||
return await this.request(`${this.pluginName}.play`, {resource: resource.url, subtitles: subs})
|
const args = {resource: resource.url, subtitles: subs, metadata: resource}
|
||||||
|
if (opts?.cache) {
|
||||||
|
args.cache_streams = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return await this.request(`${this.pluginName}.play`, args)
|
||||||
},
|
},
|
||||||
|
|
||||||
async pause() {
|
async pause() {
|
||||||
|
|
|
@ -22,6 +22,11 @@ export default {
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
loading: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
|
||||||
selectedPlaylist: {
|
selectedPlaylist: {
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
|
@ -33,8 +38,14 @@ export default {
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
loading: false,
|
loading_: false,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
isLoading() {
|
||||||
|
return this.loading || this.loading_
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="media-youtube-browser">
|
<div class="media-youtube-browser">
|
||||||
<Loading v-if="loading" />
|
<Loading v-if="loading_" />
|
||||||
|
|
||||||
<div class="browser" v-else>
|
<div class="browser" v-else>
|
||||||
<MediaNav :path="computedPath" @back="$emit('back')" />
|
<MediaNav :path="computedPath" @back="$emit('back')" />
|
||||||
|
@ -8,32 +8,38 @@
|
||||||
|
|
||||||
<div class="body" v-else>
|
<div class="body" v-else>
|
||||||
<Feed :filter="filter"
|
<Feed :filter="filter"
|
||||||
|
:loading="isLoading"
|
||||||
@add-to-playlist="$emit('add-to-playlist', $event)"
|
@add-to-playlist="$emit('add-to-playlist', $event)"
|
||||||
@download="$emit('download', $event)"
|
@download="$emit('download', $event)"
|
||||||
@download-audio="$emit('download-audio', $event)"
|
@download-audio="$emit('download-audio', $event)"
|
||||||
@open-channel="selectChannelFromItem"
|
@open-channel="selectChannelFromItem"
|
||||||
@play="$emit('play', $event)"
|
@play="$emit('play', $event)"
|
||||||
|
@play-cache="$emit('play-cache', $event)"
|
||||||
v-if="selectedView === 'feed'"
|
v-if="selectedView === 'feed'"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Playlists :filter="filter"
|
<Playlists :filter="filter"
|
||||||
|
:loading="isLoading"
|
||||||
:selected-playlist="selectedPlaylist_"
|
:selected-playlist="selectedPlaylist_"
|
||||||
@add-to-playlist="$emit('add-to-playlist', $event)"
|
@add-to-playlist="$emit('add-to-playlist', $event)"
|
||||||
@download="$emit('download', $event)"
|
@download="$emit('download', $event)"
|
||||||
@download-audio="$emit('download-audio', $event)"
|
@download-audio="$emit('download-audio', $event)"
|
||||||
@open-channel="selectChannelFromItem"
|
@open-channel="selectChannelFromItem"
|
||||||
@play="$emit('play', $event)"
|
@play="$emit('play', $event)"
|
||||||
|
@play-cache="$emit('play-cache', $event)"
|
||||||
@remove-from-playlist="removeFromPlaylist"
|
@remove-from-playlist="removeFromPlaylist"
|
||||||
@select="onPlaylistSelected"
|
@select="onPlaylistSelected"
|
||||||
v-else-if="selectedView === 'playlists'"
|
v-else-if="selectedView === 'playlists'"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Subscriptions :filter="filter"
|
<Subscriptions :filter="filter"
|
||||||
|
:loading="isLoading"
|
||||||
:selected-channel="selectedChannel_"
|
:selected-channel="selectedChannel_"
|
||||||
@add-to-playlist="$emit('add-to-playlist', $event)"
|
@add-to-playlist="$emit('add-to-playlist', $event)"
|
||||||
@download="$emit('download', $event)"
|
@download="$emit('download', $event)"
|
||||||
@download-audio="$emit('download-audio', $event)"
|
@download-audio="$emit('download-audio', $event)"
|
||||||
@play="$emit('play', $event)"
|
@play="$emit('play', $event)"
|
||||||
|
@play-cache="$emit('play-cache', $event)"
|
||||||
@select="onChannelSelected"
|
@select="onChannelSelected"
|
||||||
v-else-if="selectedView === 'subscriptions'"
|
v-else-if="selectedView === 'subscriptions'"
|
||||||
/>
|
/>
|
||||||
|
@ -98,18 +104,18 @@ export default {
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
async loadYoutubeConfig() {
|
async loadYoutubeConfig() {
|
||||||
this.loading = true
|
this.loading_ = true
|
||||||
try {
|
try {
|
||||||
this.youtubeConfig = (await this.request('config.get_plugins')).youtube
|
this.youtubeConfig = (await this.request('config.get_plugins')).youtube
|
||||||
} finally {
|
} finally {
|
||||||
this.loading = false
|
this.loading_ = false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async removeFromPlaylist(event) {
|
async removeFromPlaylist(event) {
|
||||||
const playlistId = event.playlist_id
|
const playlistId = event.playlist_id
|
||||||
const videoId = event.item.url
|
const videoId = event.item.url
|
||||||
this.loading = true
|
this.loading_ = true
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.request('youtube.remove_from_playlist', {
|
await this.request('youtube.remove_from_playlist', {
|
||||||
|
@ -117,16 +123,16 @@ export default {
|
||||||
video_id: videoId,
|
video_id: videoId,
|
||||||
})
|
})
|
||||||
} finally {
|
} finally {
|
||||||
this.loading = false
|
this.loading_ = false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async createPlaylist(name) {
|
async createPlaylist(name) {
|
||||||
this.loading = true
|
this.loading_ = true
|
||||||
try {
|
try {
|
||||||
await this.request('youtube.create_playlist', {name: name})
|
await this.request('youtube.create_playlist', {name: name})
|
||||||
} finally {
|
} finally {
|
||||||
this.loading = false
|
this.loading_ = false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="media-youtube-channel">
|
<div class="media-youtube-channel">
|
||||||
<Loading v-if="loading" />
|
<Loading v-if="isLoading" />
|
||||||
|
|
||||||
<div class="channel" v-else-if="channel">
|
<div class="channel" v-else-if="channel">
|
||||||
<div class="header">
|
<div class="header">
|
||||||
|
@ -29,7 +29,7 @@
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div class="subscribers" v-if="channel.subscribers != null && (channel.subscribers || 0) >= 0">
|
<div class="subscribers" v-if="channel.subscribers != null && (channel.subscribers || 0) >= 0">
|
||||||
{{ channel.subscribers }} subscribers
|
{{ formatNumber(channel.subscribers) }} subscribers
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -51,6 +51,7 @@
|
||||||
@download-audio="$emit('download-audio', $event)"
|
@download-audio="$emit('download-audio', $event)"
|
||||||
@open-channel="$emit('open-channel', $event)"
|
@open-channel="$emit('open-channel', $event)"
|
||||||
@play="$emit('play', $event)"
|
@play="$emit('play', $event)"
|
||||||
|
@play-cache="$emit('play-cache', $event)"
|
||||||
@scroll-end="loadNextPage"
|
@scroll-end="loadNextPage"
|
||||||
@select="selectedResult = $event"
|
@select="selectedResult = $event"
|
||||||
/>
|
/>
|
||||||
|
@ -71,6 +72,7 @@ export default {
|
||||||
'download-audio',
|
'download-audio',
|
||||||
'open-channel',
|
'open-channel',
|
||||||
'play',
|
'play',
|
||||||
|
'play-cache',
|
||||||
],
|
],
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
|
@ -88,12 +90,17 @@ export default {
|
||||||
type: String,
|
type: String,
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
loading: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
channel: null,
|
channel: null,
|
||||||
loading: false,
|
loading_: false,
|
||||||
loadingNextPage: false,
|
loadingNextPage: false,
|
||||||
selectedResult: null,
|
selectedResult: null,
|
||||||
subscribed: false,
|
subscribed: false,
|
||||||
|
@ -101,6 +108,10 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
|
isLoading() {
|
||||||
|
return this.loading || this.loading_
|
||||||
|
},
|
||||||
|
|
||||||
itemsByUrl() {
|
itemsByUrl() {
|
||||||
return this.channel?.items.reduce((acc, item) => {
|
return this.channel?.items.reduce((acc, item) => {
|
||||||
acc[item.url] = item
|
acc[item.url] = item
|
||||||
|
@ -111,12 +122,12 @@ export default {
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
async loadChannel() {
|
async loadChannel() {
|
||||||
this.loading = true
|
this.loading_ = true
|
||||||
try {
|
try {
|
||||||
await this.updateChannel(true)
|
await this.updateChannel(true)
|
||||||
this.subscribed = await this.request('youtube.is_subscribed', {channel_id: this.id})
|
this.subscribed = await this.request('youtube.is_subscribed', {channel_id: this.id})
|
||||||
} finally {
|
} finally {
|
||||||
this.loading = false
|
this.loading_ = false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="media-youtube-feed">
|
<div class="media-youtube-feed">
|
||||||
<Loading v-if="loading" />
|
<Loading v-if="isLoading" />
|
||||||
<NoItems :with-shadow="false" v-else-if="!feed?.length">
|
<NoItems :with-shadow="false" v-else-if="!feed?.length">
|
||||||
No videos found.
|
No videos found.
|
||||||
</NoItems>
|
</NoItems>
|
||||||
|
@ -15,6 +15,7 @@
|
||||||
@open-channel="$emit('open-channel', $event)"
|
@open-channel="$emit('open-channel', $event)"
|
||||||
@select="selectedResult = $event"
|
@select="selectedResult = $event"
|
||||||
@play="$emit('play', $event)"
|
@play="$emit('play', $event)"
|
||||||
|
@play-cache="$emit('play-cache', $event)"
|
||||||
v-else />
|
v-else />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -33,6 +34,7 @@ export default {
|
||||||
'download-audio',
|
'download-audio',
|
||||||
'open-channel',
|
'open-channel',
|
||||||
'play',
|
'play',
|
||||||
|
'play-cache',
|
||||||
],
|
],
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
|
@ -46,26 +48,37 @@ export default {
|
||||||
type: String,
|
type: String,
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
loading: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
feed: [],
|
feed: [],
|
||||||
loading: false,
|
loading_: false,
|
||||||
selectedResult: null,
|
selectedResult: null,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
isLoading() {
|
||||||
|
return this.loading_ || this.loading
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
async loadFeed() {
|
async loadFeed() {
|
||||||
this.loading = true
|
this.loading_ = true
|
||||||
try {
|
try {
|
||||||
this.feed = (await this.request('youtube.get_feed')).map(item => ({
|
this.feed = (await this.request('youtube.get_feed')).map(item => ({
|
||||||
...item,
|
...item,
|
||||||
type: 'youtube',
|
type: 'youtube',
|
||||||
}))
|
}))
|
||||||
} finally {
|
} finally {
|
||||||
this.loading = false
|
this.loading_ = false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -54,6 +54,7 @@
|
||||||
@download-audio="$emit('download-audio', $event)"
|
@download-audio="$emit('download-audio', $event)"
|
||||||
@open-channel="$emit('open-channel', $event)"
|
@open-channel="$emit('open-channel', $event)"
|
||||||
@play="$emit('play', $event)"
|
@play="$emit('play', $event)"
|
||||||
|
@play-cache="$emit('play-cache', $event)"
|
||||||
@remove-from-playlist="$emit('remove-from-playlist', $event)"
|
@remove-from-playlist="$emit('remove-from-playlist', $event)"
|
||||||
@select="selectedResult = $event"
|
@select="selectedResult = $event"
|
||||||
v-else />
|
v-else />
|
||||||
|
@ -75,6 +76,7 @@ export default {
|
||||||
'download-audio',
|
'download-audio',
|
||||||
'open-channel',
|
'open-channel',
|
||||||
'play',
|
'play',
|
||||||
|
'play-cache',
|
||||||
'remove-from-playlist',
|
'remove-from-playlist',
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="media-youtube-playlists">
|
<div class="media-youtube-playlists">
|
||||||
<div class="playlists-index" v-if="!selectedPlaylist?.id">
|
<div class="playlists-index" v-if="!selectedPlaylist?.id">
|
||||||
<Loading v-if="loading" />
|
<Loading v-if="isLoading" />
|
||||||
<NoItems :with-shadow="false" v-else-if="!playlists?.length">
|
<NoItems :with-shadow="false" v-else-if="!playlists?.length">
|
||||||
No playlists found.
|
No playlists found.
|
||||||
</NoItems>
|
</NoItems>
|
||||||
|
@ -36,6 +36,7 @@
|
||||||
@open-channel="$emit('open-channel', $event)"
|
@open-channel="$emit('open-channel', $event)"
|
||||||
@remove-from-playlist="$emit('remove-from-playlist', {item: $event, playlist_id: selectedPlaylist.id})"
|
@remove-from-playlist="$emit('remove-from-playlist', {item: $event, playlist_id: selectedPlaylist.id})"
|
||||||
@play="$emit('play', $event)"
|
@play="$emit('play', $event)"
|
||||||
|
@play-cache="$emit('play-cache', $event)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -117,6 +118,7 @@ export default {
|
||||||
'download-audio',
|
'download-audio',
|
||||||
'open-channel',
|
'open-channel',
|
||||||
'play',
|
'play',
|
||||||
|
'play-cache',
|
||||||
'remove-from-playlist',
|
'remove-from-playlist',
|
||||||
'remove-playlist',
|
'remove-playlist',
|
||||||
'rename-playlist',
|
'rename-playlist',
|
||||||
|
@ -144,6 +146,11 @@ export default {
|
||||||
type: String,
|
type: String,
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
loading: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
|
@ -153,7 +160,7 @@ export default {
|
||||||
editedPlaylistName: '',
|
editedPlaylistName: '',
|
||||||
editedPlaylistDescription: '',
|
editedPlaylistDescription: '',
|
||||||
playlists: [],
|
playlists: [],
|
||||||
loading: false,
|
loading_: false,
|
||||||
showCreatePlaylist: false,
|
showCreatePlaylist: false,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -167,26 +174,30 @@ export default {
|
||||||
return acc
|
return acc
|
||||||
}, {})
|
}, {})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
isLoading() {
|
||||||
|
return this.loading_ || this.loading
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
async loadPlaylists() {
|
async loadPlaylists() {
|
||||||
this.loading = true
|
this.loading_ = true
|
||||||
try {
|
try {
|
||||||
this.playlists = (await this.request('youtube.get_playlists'))
|
this.playlists = (await this.request('youtube.get_playlists'))
|
||||||
} finally {
|
} finally {
|
||||||
this.loading = false
|
this.loading_ = false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async createPlaylist(name) {
|
async createPlaylist(name) {
|
||||||
this.loading = true
|
this.loading_ = true
|
||||||
try {
|
try {
|
||||||
await this.request('youtube.create_playlist', {name: name})
|
await this.request('youtube.create_playlist', {name: name})
|
||||||
this.showCreatePlaylist = false
|
this.showCreatePlaylist = false
|
||||||
this.loadPlaylists()
|
this.loadPlaylists()
|
||||||
} finally {
|
} finally {
|
||||||
this.loading = false
|
this.loading_ = false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -194,13 +205,13 @@ export default {
|
||||||
if (!this.deletedPlaylist)
|
if (!this.deletedPlaylist)
|
||||||
return
|
return
|
||||||
|
|
||||||
this.loading = true
|
this.loading_ = true
|
||||||
try {
|
try {
|
||||||
await this.request('youtube.delete_playlist', {id: this.deletedPlaylist})
|
await this.request('youtube.delete_playlist', {id: this.deletedPlaylist})
|
||||||
this.deletedPlaylist = null
|
this.deletedPlaylist = null
|
||||||
this.loadPlaylists()
|
this.loadPlaylists()
|
||||||
} finally {
|
} finally {
|
||||||
this.loading = false
|
this.loading_ = false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -208,7 +219,7 @@ export default {
|
||||||
if (!this.editedPlaylist)
|
if (!this.editedPlaylist)
|
||||||
return
|
return
|
||||||
|
|
||||||
this.loading = true
|
this.loading_ = true
|
||||||
try {
|
try {
|
||||||
await this.request('youtube.rename_playlist', {
|
await this.request('youtube.rename_playlist', {
|
||||||
id: this.editedPlaylist,
|
id: this.editedPlaylist,
|
||||||
|
@ -219,7 +230,7 @@ export default {
|
||||||
this.clearEditPlaylist()
|
this.clearEditPlaylist()
|
||||||
this.loadPlaylists()
|
this.loadPlaylists()
|
||||||
} finally {
|
} finally {
|
||||||
this.loading = false
|
this.loading_ = false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,7 @@
|
||||||
@download="$emit('download', $event)"
|
@download="$emit('download', $event)"
|
||||||
@download-audio="$emit('download-audio', $event)"
|
@download-audio="$emit('download-audio', $event)"
|
||||||
@play="$emit('play', $event)"
|
@play="$emit('play', $event)"
|
||||||
|
@play-cache="$emit('play-cache', $event)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -45,6 +46,7 @@ export default {
|
||||||
'download',
|
'download',
|
||||||
'download-audio',
|
'download-audio',
|
||||||
'play',
|
'play',
|
||||||
|
'play-cache',
|
||||||
'select',
|
'select',
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
@remove-from-playlist="$emit('remove-from-playlist', item)"
|
@remove-from-playlist="$emit('remove-from-playlist', item)"
|
||||||
@select="$emit('select', i)"
|
@select="$emit('select', i)"
|
||||||
@play="$emit('play', item)"
|
@play="$emit('play', item)"
|
||||||
|
@play-cache="$emit('play-cache', item)"
|
||||||
@view="$emit('view', item)"
|
@view="$emit('view', item)"
|
||||||
@download="$emit('download', item)"
|
@download="$emit('download', item)"
|
||||||
@download-audio="$emit('download-audio', item)"
|
@download-audio="$emit('download-audio', item)"
|
||||||
|
@ -22,7 +23,12 @@
|
||||||
<Modal ref="infoModal" title="Media info" @close="$emit('select', null)">
|
<Modal ref="infoModal" title="Media info" @close="$emit('select', null)">
|
||||||
<Info :item="results[selectedResult]"
|
<Info :item="results[selectedResult]"
|
||||||
:pluginName="pluginName"
|
:pluginName="pluginName"
|
||||||
|
@add-to-playlist="$emit('add-to-playlist', results[selectedResult])"
|
||||||
|
@download="$emit('download', results[selectedResult])"
|
||||||
|
@download-audio="$emit('download-audio', results[selectedResult])"
|
||||||
|
@open-channel="$emit('open-channel', results[selectedResult])"
|
||||||
@play="$emit('play', results[selectedResult])"
|
@play="$emit('play', results[selectedResult])"
|
||||||
|
@play-cache="$emit('play-cache', results[selectedResult])"
|
||||||
v-if="selectedResult != null" />
|
v-if="selectedResult != null" />
|
||||||
</Modal>
|
</Modal>
|
||||||
</div>
|
</div>
|
||||||
|
@ -42,6 +48,7 @@ export default {
|
||||||
'download-audio',
|
'download-audio',
|
||||||
'open-channel',
|
'open-channel',
|
||||||
'play',
|
'play',
|
||||||
|
'play-cache',
|
||||||
'remove-from-playlist',
|
'remove-from-playlist',
|
||||||
'scroll-end',
|
'scroll-end',
|
||||||
'select',
|
'select',
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
<template>
|
<template>
|
||||||
<Media plugin-name="media.omxplayer" />
|
<Media plugin-name="media.chromecast" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Media from '@/components/panels/Media/Index'
|
import Media from '@/components/panels/Media/Index'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "MediaMpv",
|
|
||||||
components: {Media},
|
components: {Media},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
|
@ -0,0 +1,15 @@
|
||||||
|
<template>
|
||||||
|
<Media plugin-name="media.gstreamer" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Media from '@/components/panels/Media/Index'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {Media},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
|
@ -16,6 +16,10 @@ export default {
|
||||||
indent(text, spaces = 2) {
|
indent(text, spaces = 2) {
|
||||||
return text.split('\n').map((t) => `${' '.repeat(spaces)}${t}`).join('\n')
|
return text.split('\n').map((t) => `${' '.repeat(spaces)}${t}`).join('\n')
|
||||||
},
|
},
|
||||||
|
|
||||||
|
formatNumber(number) {
|
||||||
|
return number.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
Loading…
Reference in a new issue