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": {
|
||||
"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"
|
||||
},
|
||||
|
|
|
@ -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%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 = ''
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]">
|
||||
|
||||
</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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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' ||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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',
|
||||
}
|
||||
},
|
||||
}
|
|
@ -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() {
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
},
|
||||
},
|
||||
|
|
|
@ -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',
|
||||
],
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -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',
|
||||
],
|
||||
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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>
|
|
@ -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) {
|
||||
return text.split('\n').map((t) => `${' '.repeat(spaces)}${t}`).join('\n')
|
||||
},
|
||||
|
||||
formatNumber(number) {
|
||||
return number.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
|
Loading…
Reference in a new issue