[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:
Fabio Manganiello 2024-08-18 13:03:04 +02:00
parent a21aaee888
commit 01571e2e65
Signed by untrusted user: blacklight
GPG key ID: D90FBA7F76362774
24 changed files with 307 additions and 105 deletions

View file

@ -53,15 +53,15 @@
"linode": {
"class": "fas fa-cloud"
},
"media.chromecast": {
"class": "fab fa-chromecast"
},
"media.jellyfin": {
"imgUrl": "/icons/jellyfin.svg"
},
"media.kodi": {
"imgUrl": "/icons/kodi.svg"
},
"media.omxplayer": {
"class": "fa fa-film"
},
"media.mplayer": {
"class": "fa fa-film"
},

View file

@ -57,7 +57,7 @@
</div>
<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">
<img class="image from desktop" :src="trackImage" :alt="track.title">
</div>
@ -127,6 +127,7 @@ export default {
mixins: [Utils, MediaUtils],
emits: [
'consume',
'info',
'mute',
'next',
'pause',
@ -202,6 +203,9 @@ export default {
},
trackImage() {
if (this.track?.images?.length)
return this.track.images[0].url
return this.track?.image || this.image
},
},
@ -405,6 +409,10 @@ button {
overflow: hidden;
align-items: center;
button {
background: none !important;
}
.row {
width: 100%;
display: flex;
@ -466,8 +474,14 @@ button {
flex-direction: row;
align-items: center;
.img-container {
max-width: 100%;
max-height: calc(100% + 3em);
}
.image {
margin-right: 0.75em;
padding: 0.5em;
max-height: 100%;
}
}
}

View file

@ -9,6 +9,7 @@
:status="status"
:track="track"
@consume="$emit('consume', $event)"
@info="$emit('info', $event)"
@mute="$emit('mute')"
@next="$emit('next')"
@pause="$emit('pause', $event)"
@ -33,6 +34,7 @@ export default {
components: {Controls},
emits: [
'consume',
'info',
'mute',
'next',
'pause',

View file

@ -1,9 +1,7 @@
<template>
<keep-alive>
<div class="media-browser">
<Loading v-if="loading" />
<div class="media-index grid" v-else-if="!mediaProvider">
<div class="media-index grid" v-if="!mediaProvider">
<div class="item"
v-for="(provider, name) in mediaProviders"
:key="name"
@ -19,10 +17,11 @@
</div>
</div>
<div class="media-browser-body" v-else-if="mediaProvider">
<div class="media-browser-body" v-if="mediaProvider">
<component
:is="mediaProvider"
:filter="filter"
:loading="loading"
:selected-playlist="selectedPlaylist"
:selected-channel="selectedChannel"
@add-to-playlist="$emit('add-to-playlist', $event)"
@ -31,6 +30,7 @@
@download-audio="$emit('download-audio', $event)"
@path-change="$emit('path-change', $event)"
@play="$emit('play', $event)"
@play-cache="$emit('play-cache', $event)"
/>
</div>
</div>
@ -40,7 +40,6 @@
<script>
import { defineAsyncComponent, ref } from "vue";
import Browser from "@/components/File/Browser";
import Loading from "@/components/Loading";
import Utils from "@/Utils";
import providersMetadata from "./Providers/meta.json";
@ -54,6 +53,7 @@ export default {
'download-audio',
'path-change',
'play',
'play-cache',
'remove-from-playlist',
'remove-playlist',
'rename-playlist',
@ -61,7 +61,6 @@ export default {
components: {
Browser,
Loading,
},
props: {
@ -77,11 +76,15 @@ export default {
selectedChannel: {
type: Object,
},
loading: {
type: Boolean,
default: false,
},
},
data() {
return {
loading: false,
mediaProvider: null,
mediaProviders: {},
providersMetadata: providersMetadata,

View file

@ -67,10 +67,13 @@
</template>
<script>
import Players from "@/components/panels/Media/Players";
import Players from "@/components/panels/Media/Players"
import Utils from '@/Utils'
export default {
name: "Header",
components: {Players},
mixins: [Utils],
emits: [
'filter',
'filter-downloads',
@ -132,20 +135,36 @@ export default {
}
},
computed: {
enabledTypes() {
return Object.keys(this.sources).filter((source) => this.sources[source])
},
},
methods: {
search() {
const types = Object.keys(this.sources).filter((source) => this.sources[source])
if (!this.query?.length || !types?.length)
if (!this.query?.length || !this.enabledTypes?.length)
return
this.$emit('search', {
query: this.query,
types: types,
types: this.enabledTypes,
})
},
},
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.$emit('filter', '')
this.torrentURL = ''

View file

@ -5,6 +5,7 @@
:status="selectedPlayer?.status || {}"
:track="selectedPlayer?.status || {}"
:buttons="mediaButtons"
@info="infoTrack = $event"
@play="pause"
@pause="pause"
@stop="stop"
@ -58,6 +59,7 @@
@open-channel="selectChannelFromItem"
@select="onResultSelect($event)"
@play="play"
@play-cache="play($event, {cache: true})"
@view="view"
@download="download"
@download-audio="downloadAudio"
@ -78,6 +80,7 @@
/>
<Browser :filter="browserFilter"
:loading="loading"
:selected-playlist="selectedPlaylist"
:selected-channel="selectedChannel"
@add-to-playlist="addToPlaylistItem = $event"
@ -86,6 +89,7 @@
@download-audio="downloadAudio"
@path-change="browserFilter = ''"
@play="play($event)"
@play-cache="play($event, {cache: true})"
v-else-if="selectedView === 'browser'"
/>
</div>
@ -119,6 +123,20 @@
/>
</Modal>
</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>
</keep-alive>
</template>
@ -129,6 +147,7 @@ import Utils from "@/Utils";
import Browser from "@/components/panels/Media/Browser";
import Header from "@/components/panels/Media/Header";
import Info from "@/components/panels/Media/Info";
import MediaDownloads from "@/components/panels/Media/Downloads";
import MediaUtils from "@/components/Media/Utils";
import MediaView from "@/components/Media/View";
@ -145,6 +164,7 @@ export default {
components: {
Browser,
Header,
Info,
MediaDownloads,
MediaView,
Modal,
@ -176,33 +196,32 @@ export default {
data() {
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,
torrentPlugin: null,
torrentPlugins: [
'torrent',
'rtorrent',
],
awaitingPlayTorrent: null,
browserFilter: null,
downloads: {},
downloadsFilter: null,
forceShowNav: false,
infoTrack: null,
loading: false,
prevSelectedView: null,
results: [],
selectedPlayer: null,
selectedResult: null,
selectedSubtitles: null,
selectedView: 'search',
showSubtitlesModal: false,
sources: {
'file': true,
'youtube': true,
'torrent': true,
},
downloads: {},
urlPlay: null,
torrentPlugin: null,
torrentPlugins: [
'torrent',
'rtorrent',
],
}
},
@ -281,6 +300,7 @@ export default {
methods: {
async search(event) {
this.loading = true
this.setUrlArgs({q: event.query})
try {
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') {
this.awaitingPlayTorrent = item.url
this.notify({
@ -309,7 +329,10 @@ export default {
if (!this.selectedPlayer.component.supports(item))
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()
} finally {
this.loading = false

View file

@ -1,28 +1,25 @@
<template>
<div class="media-info">
<div class="row header">
<div class="image-container">
<MediaImage :item="item" @play="$emit('play')" @select="$emit('select')" />
</div>
<div class="title">
<i :class="typeIcons[item.type]"
:title="item.type"
v-if="typeIcons[item?.type]">
&nbsp;
</i>
<a :href="item.url" target="_blank" v-if="item.url" v-text="item.title" />
<span v-else v-text="item.title" />
<div class="item-container">
<Item :item="item"
@add-to-playlist="$emit('add-to-playlist', item)"
@open-channel="$emit('open-channel', item)"
@play="$emit('play', item)"
@play-cache="$emit('play-cache', item)"
@download="$emit('download', item)"
@download-audio="$emit('download-audio', item)"
/>
</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="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" />
</a>
<button @click="copyToClipboard(directUrl)" title="Copy URL to clipboard">
<button @click="copyToClipboard(mainUrl)" title="Copy URL to clipboard">
<i class="fas fa-clipboard" />
</button>
</div>
@ -85,6 +82,11 @@
</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="left side">Rating</div>
<div class="right side">{{ item.rating }}%</div>
@ -163,20 +165,34 @@
<div class="left side">Language</div>
<div class="right side" v-text="item.language" />
</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>
</template>
<script>
import Utils from "@/Utils";
import MediaUtils from "@/components/Media/Utils";
import MediaImage from "./MediaImage";
import Icons from "./icons.json";
import Item from "./Item";
import MediaUtils from "@/components/Media/Utils";
import Utils from "@/Utils";
export default {
name: "Info",
components: {MediaImage},
components: {
Item,
},
mixins: [Utils, MediaUtils],
emits: ['play'],
emits: [
'add-to-playlist',
'download',
'download-audio',
'open-channel',
'play',
'play-cache',
],
props: {
item: {
type: Object,
@ -224,6 +240,8 @@ export default {
return this.formatDate(this.item.publishedAt, true)
if (this.item?.created_at)
return this.formatDate(this.item.created_at, true)
if (this.item?.timestamp)
return this.formatDate(this.item.timestamp, true)
return null
},
@ -236,6 +254,14 @@ export default {
return null
},
mainUrl() {
const directUrl = this.directUrl
if (directUrl)
return directUrl
return this.item?.url
},
},
}
</script>
@ -331,11 +357,9 @@ export default {
flex-direction: column;
position: relative;
.image-container {
.item-container {
@include from($desktop) {
.image-container {
width: 420px;
}
width: 420px;
}
}

View file

@ -15,6 +15,8 @@
<Dropdown title="Actions" icon-class="fa fa-ellipsis-h" ref="dropdown">
<DropdownItem icon-class="fa fa-play" text="Play" @input="$emit('play')"
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')"
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')"
@ -60,6 +62,7 @@ export default {
'download-audio',
'open-channel',
'play',
'play-cache',
'remove-from-playlist',
'select',
'view',

View file

@ -13,7 +13,7 @@
</a>
</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="inner">
<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() {
if (
this.item?.item_type === 'channel' ||

View file

@ -9,8 +9,8 @@
@status="$emit('status', $event)" />
<Mpv :player="selectedPlayer?.pluginName === 'media.mpv' ? selectedPlayer : null" ref="mpvPlugin"
@status="$emit('status', $event)" />
<Omxplayer :player="selectedPlayer?.pluginName === 'media.omxplayer' ? selectedPlayer : null" ref="omxplayerPlugin"
@status="$emit('status', $event)" />
<GStreamer :player="selectedPlayer?.pluginName === 'media.gstreamer' ? selectedPlayer : null"
ref="gstreamerPlugin" @status="$emit('status', $event)" />
<Vlc :player="selectedPlayer?.pluginName === 'media.vlc' ? selectedPlayer : null" ref="vlcPlugin"
@status="$emit('status', $event)" />
</div>
@ -40,18 +40,20 @@
import Dropdown from "@/components/elements/Dropdown";
import DropdownItem from "@/components/elements/DropdownItem";
import Loading from "@/components/Loading";
import Utils from '@/Utils'
import Chromecast from "@/components/panels/Media/Players/Chromecast"
import Kodi from "@/components/panels/Media/Players/Kodi";
import Mplayer from "@/components/panels/Media/Players/Mplayer";
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";
export default {
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'],
mixins: [Utils],
props: {
pluginName: {
@ -89,7 +91,16 @@ export default {
this.players.push(...players)
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 {

View file

@ -8,6 +8,7 @@ import Mixin from "@/components/panels/Media/Players/Mixin";
export default {
name: "Chromecast",
mixins: [Mixin],
emits: ['status'],
data() {
return {
name: 'Chromecast',
@ -48,12 +49,20 @@ export default {
)?.status
},
async play(resource, player) {
async play(resource, subs, player) {
if (!resource) {
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) {

View file

@ -6,13 +6,12 @@
import Mixin from "@/components/panels/Media/Players/Mixin";
export default {
name: "Omxplayer",
mixins: [Mixin],
data() {
return {
iconClass: 'fa fa-tv',
name: 'OMXPlayer',
pluginName: 'media.omxplayer',
name: 'GStreamer',
pluginName: 'media.gstreamer',
}
},
}

View file

@ -36,12 +36,17 @@ export default {
return await this.request(`${this.pluginName}.status`)
},
async play(resource, subs) {
async play(resource, subs, _, opts) {
if (!resource) {
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() {

View file

@ -22,6 +22,11 @@ export default {
default: '',
},
loading: {
type: Boolean,
default: false,
},
selectedPlaylist: {
default: null,
},
@ -33,8 +38,14 @@ export default {
data() {
return {
loading: false,
loading_: false,
}
},
computed: {
isLoading() {
return this.loading || this.loading_
},
},
}
</script>

View file

@ -1,6 +1,6 @@
<template>
<div class="media-youtube-browser">
<Loading v-if="loading" />
<Loading v-if="loading_" />
<div class="browser" v-else>
<MediaNav :path="computedPath" @back="$emit('back')" />
@ -8,32 +8,38 @@
<div class="body" v-else>
<Feed :filter="filter"
:loading="isLoading"
@add-to-playlist="$emit('add-to-playlist', $event)"
@download="$emit('download', $event)"
@download-audio="$emit('download-audio', $event)"
@open-channel="selectChannelFromItem"
@play="$emit('play', $event)"
@play-cache="$emit('play-cache', $event)"
v-if="selectedView === 'feed'"
/>
<Playlists :filter="filter"
:loading="isLoading"
:selected-playlist="selectedPlaylist_"
@add-to-playlist="$emit('add-to-playlist', $event)"
@download="$emit('download', $event)"
@download-audio="$emit('download-audio', $event)"
@open-channel="selectChannelFromItem"
@play="$emit('play', $event)"
@play-cache="$emit('play-cache', $event)"
@remove-from-playlist="removeFromPlaylist"
@select="onPlaylistSelected"
v-else-if="selectedView === 'playlists'"
/>
<Subscriptions :filter="filter"
:loading="isLoading"
:selected-channel="selectedChannel_"
@add-to-playlist="$emit('add-to-playlist', $event)"
@download="$emit('download', $event)"
@download-audio="$emit('download-audio', $event)"
@play="$emit('play', $event)"
@play-cache="$emit('play-cache', $event)"
@select="onChannelSelected"
v-else-if="selectedView === 'subscriptions'"
/>
@ -98,18 +104,18 @@ export default {
methods: {
async loadYoutubeConfig() {
this.loading = true
this.loading_ = true
try {
this.youtubeConfig = (await this.request('config.get_plugins')).youtube
} finally {
this.loading = false
this.loading_ = false
}
},
async removeFromPlaylist(event) {
const playlistId = event.playlist_id
const videoId = event.item.url
this.loading = true
this.loading_ = true
try {
await this.request('youtube.remove_from_playlist', {
@ -117,16 +123,16 @@ export default {
video_id: videoId,
})
} finally {
this.loading = false
this.loading_ = false
}
},
async createPlaylist(name) {
this.loading = true
this.loading_ = true
try {
await this.request('youtube.create_playlist', {name: name})
} finally {
this.loading = false
this.loading_ = false
}
},

View file

@ -1,6 +1,6 @@
<template>
<div class="media-youtube-channel">
<Loading v-if="loading" />
<Loading v-if="isLoading" />
<div class="channel" v-else-if="channel">
<div class="header">
@ -29,7 +29,7 @@
</button>
<div class="subscribers" v-if="channel.subscribers != null && (channel.subscribers || 0) >= 0">
{{ channel.subscribers }} subscribers
{{ formatNumber(channel.subscribers) }} subscribers
</div>
</div>
</div>
@ -51,6 +51,7 @@
@download-audio="$emit('download-audio', $event)"
@open-channel="$emit('open-channel', $event)"
@play="$emit('play', $event)"
@play-cache="$emit('play-cache', $event)"
@scroll-end="loadNextPage"
@select="selectedResult = $event"
/>
@ -71,6 +72,7 @@ export default {
'download-audio',
'open-channel',
'play',
'play-cache',
],
components: {
@ -88,12 +90,17 @@ export default {
type: String,
default: null,
},
loading: {
type: Boolean,
default: false,
},
},
data() {
return {
channel: null,
loading: false,
loading_: false,
loadingNextPage: false,
selectedResult: null,
subscribed: false,
@ -101,6 +108,10 @@ export default {
},
computed: {
isLoading() {
return this.loading || this.loading_
},
itemsByUrl() {
return this.channel?.items.reduce((acc, item) => {
acc[item.url] = item
@ -111,12 +122,12 @@ export default {
methods: {
async loadChannel() {
this.loading = true
this.loading_ = true
try {
await this.updateChannel(true)
this.subscribed = await this.request('youtube.is_subscribed', {channel_id: this.id})
} finally {
this.loading = false
this.loading_ = false
}
},

View file

@ -1,6 +1,6 @@
<template>
<div class="media-youtube-feed">
<Loading v-if="loading" />
<Loading v-if="isLoading" />
<NoItems :with-shadow="false" v-else-if="!feed?.length">
No videos found.
</NoItems>
@ -15,6 +15,7 @@
@open-channel="$emit('open-channel', $event)"
@select="selectedResult = $event"
@play="$emit('play', $event)"
@play-cache="$emit('play-cache', $event)"
v-else />
</div>
</template>
@ -33,6 +34,7 @@ export default {
'download-audio',
'open-channel',
'play',
'play-cache',
],
components: {
@ -46,26 +48,37 @@ export default {
type: String,
default: null,
},
loading: {
type: Boolean,
default: false,
},
},
data() {
return {
feed: [],
loading: false,
loading_: false,
selectedResult: null,
}
},
computed: {
isLoading() {
return this.loading_ || this.loading
},
},
methods: {
async loadFeed() {
this.loading = true
this.loading_ = true
try {
this.feed = (await this.request('youtube.get_feed')).map(item => ({
...item,
type: 'youtube',
}))
} finally {
this.loading = false
this.loading_ = false
}
},
},

View file

@ -54,6 +54,7 @@
@download-audio="$emit('download-audio', $event)"
@open-channel="$emit('open-channel', $event)"
@play="$emit('play', $event)"
@play-cache="$emit('play-cache', $event)"
@remove-from-playlist="$emit('remove-from-playlist', $event)"
@select="selectedResult = $event"
v-else />
@ -75,6 +76,7 @@ export default {
'download-audio',
'open-channel',
'play',
'play-cache',
'remove-from-playlist',
],

View file

@ -1,7 +1,7 @@
<template>
<div class="media-youtube-playlists">
<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">
No playlists found.
</NoItems>
@ -36,6 +36,7 @@
@open-channel="$emit('open-channel', $event)"
@remove-from-playlist="$emit('remove-from-playlist', {item: $event, playlist_id: selectedPlaylist.id})"
@play="$emit('play', $event)"
@play-cache="$emit('play-cache', $event)"
/>
</div>
@ -117,6 +118,7 @@ export default {
'download-audio',
'open-channel',
'play',
'play-cache',
'remove-from-playlist',
'remove-playlist',
'rename-playlist',
@ -144,6 +146,11 @@ export default {
type: String,
default: null,
},
loading: {
type: Boolean,
default: false,
},
},
data() {
@ -153,7 +160,7 @@ export default {
editedPlaylistName: '',
editedPlaylistDescription: '',
playlists: [],
loading: false,
loading_: false,
showCreatePlaylist: false,
}
},
@ -167,26 +174,30 @@ export default {
return acc
}, {})
},
isLoading() {
return this.loading_ || this.loading
},
},
methods: {
async loadPlaylists() {
this.loading = true
this.loading_ = true
try {
this.playlists = (await this.request('youtube.get_playlists'))
} finally {
this.loading = false
this.loading_ = false
}
},
async createPlaylist(name) {
this.loading = true
this.loading_ = true
try {
await this.request('youtube.create_playlist', {name: name})
this.showCreatePlaylist = false
this.loadPlaylists()
} finally {
this.loading = false
this.loading_ = false
}
},
@ -194,13 +205,13 @@ export default {
if (!this.deletedPlaylist)
return
this.loading = true
this.loading_ = true
try {
await this.request('youtube.delete_playlist', {id: this.deletedPlaylist})
this.deletedPlaylist = null
this.loadPlaylists()
} finally {
this.loading = false
this.loading_ = false
}
},
@ -208,7 +219,7 @@ export default {
if (!this.editedPlaylist)
return
this.loading = true
this.loading_ = true
try {
await this.request('youtube.rename_playlist', {
id: this.editedPlaylist,
@ -219,7 +230,7 @@ export default {
this.clearEditPlaylist()
this.loadPlaylists()
} finally {
this.loading = false
this.loading_ = false
}
},

View file

@ -27,6 +27,7 @@
@download="$emit('download', $event)"
@download-audio="$emit('download-audio', $event)"
@play="$emit('play', $event)"
@play-cache="$emit('play-cache', $event)"
/>
</div>
</div>
@ -45,6 +46,7 @@ export default {
'download',
'download-audio',
'play',
'play-cache',
'select',
],

View file

@ -13,6 +13,7 @@
@remove-from-playlist="$emit('remove-from-playlist', item)"
@select="$emit('select', i)"
@play="$emit('play', item)"
@play-cache="$emit('play-cache', item)"
@view="$emit('view', item)"
@download="$emit('download', item)"
@download-audio="$emit('download-audio', item)"
@ -22,7 +23,12 @@
<Modal ref="infoModal" title="Media info" @close="$emit('select', null)">
<Info :item="results[selectedResult]"
: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-cache="$emit('play-cache', results[selectedResult])"
v-if="selectedResult != null" />
</Modal>
</div>
@ -42,6 +48,7 @@ export default {
'download-audio',
'open-channel',
'play',
'play-cache',
'remove-from-playlist',
'scroll-end',
'select',

View file

@ -1,12 +1,11 @@
<template>
<Media plugin-name="media.omxplayer" />
<Media plugin-name="media.chromecast" />
</template>
<script>
import Media from '@/components/panels/Media/Index'
export default {
name: "MediaMpv",
components: {Media},
}
</script>

View file

@ -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>

View file

@ -16,6 +16,10 @@ export default {
indent(text, spaces = 2) {
return text.split('\n').map((t) => `${' '.repeat(spaces)}${t}`).join('\n')
},
formatNumber(number) {
return number.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")
},
},
}
</script>