forked from platypush/platypush
[#414] Added embedded player support for Jellyfin and YouTube media.
This commit is contained in:
parent
4015cf356d
commit
d171795e7c
13 changed files with 205 additions and 3 deletions
|
@ -35,6 +35,7 @@
|
||||||
@path-change="$emit('path-change', $event)"
|
@path-change="$emit('path-change', $event)"
|
||||||
@play="$emit('play', $event)"
|
@play="$emit('play', $event)"
|
||||||
@play-with-opts="$emit('play-with-opts', $event)"
|
@play-with-opts="$emit('play-with-opts', $event)"
|
||||||
|
@view="$emit('view', $event)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -61,6 +62,7 @@ export default {
|
||||||
'remove-from-playlist',
|
'remove-from-playlist',
|
||||||
'remove-playlist',
|
'remove-playlist',
|
||||||
'rename-playlist',
|
'rename-playlist',
|
||||||
|
'view',
|
||||||
],
|
],
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
|
|
|
@ -0,0 +1,158 @@
|
||||||
|
<template>
|
||||||
|
<div class="embed-player">
|
||||||
|
<div class="player-container" :class="{ youtube: !!youtubeUrl }">
|
||||||
|
<Loading v-if="loading" />
|
||||||
|
|
||||||
|
<iframe :src="youtubeUrl"
|
||||||
|
class="player"
|
||||||
|
allowfullscreen
|
||||||
|
frameborder="0"
|
||||||
|
v-else-if="youtubeUrl" />
|
||||||
|
|
||||||
|
<video ref="video"
|
||||||
|
class="player"
|
||||||
|
controls
|
||||||
|
:poster="poster"
|
||||||
|
@canplay="$refs.video.play()"
|
||||||
|
@ended="$emit('ended')"
|
||||||
|
v-else-if="mediaItem">
|
||||||
|
<source :src="mediaItem.url" :type="mediaItem.mime_type">
|
||||||
|
</video>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import axios from 'axios'
|
||||||
|
import Loading from "@/components/Loading";
|
||||||
|
import MediaUtils from "@/components/Media/Utils";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
Loading,
|
||||||
|
},
|
||||||
|
emits: ['ended'],
|
||||||
|
mixins: [MediaUtils],
|
||||||
|
props: {
|
||||||
|
item: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
pluginName: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
loading: false,
|
||||||
|
mediaItem: null,
|
||||||
|
mimeType: null,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
youtubeUrl() {
|
||||||
|
if (this.item.type !== 'youtube')
|
||||||
|
return null
|
||||||
|
|
||||||
|
const youtubeId = this.item.url.match(/(?:\?v=|\/embed\/|\/\d\/|\/vi\/|\/v\/|https?:\/\/(?:www\.)?youtu\.be\/)([^?&"'>]+)/)[1]
|
||||||
|
return `https://www.youtube-nocookie.com/embed/${youtubeId}?autoplay=1`
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
async refresh() {
|
||||||
|
this.loading = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (this.item.type === 'file') {
|
||||||
|
let streamable = null
|
||||||
|
let error = false
|
||||||
|
this.loading = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
streamable = await this.startStreaming(this.item.url, this.pluginName)
|
||||||
|
} catch (e) {
|
||||||
|
error = true
|
||||||
|
} finally {
|
||||||
|
this.opening = false
|
||||||
|
if (!streamable) {
|
||||||
|
this.notify({
|
||||||
|
title: 'Error starting streaming',
|
||||||
|
text: error || 'Unknown error',
|
||||||
|
error: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!streamable)
|
||||||
|
return
|
||||||
|
|
||||||
|
this.mediaItem = {
|
||||||
|
...this.item,
|
||||||
|
url: streamable.url,
|
||||||
|
mime_type: streamable.mime_type,
|
||||||
|
}
|
||||||
|
} else if (this.item.type !== 'youtube') {
|
||||||
|
const response = await axios.head(this.item.url)
|
||||||
|
this.mediaItem = {
|
||||||
|
...this.item,
|
||||||
|
mime_type: response.headers["content-type"],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
this.loading = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
item: {
|
||||||
|
handler() {
|
||||||
|
this.refresh()
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.refresh()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import "~@/components/Media/vars";
|
||||||
|
|
||||||
|
.embed-player {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: black;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.player-container {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
&.youtube {
|
||||||
|
min-width: 95vw;
|
||||||
|
min-height: 90vh;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
video {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
iframe {
|
||||||
|
width: 95%;
|
||||||
|
height: 95%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -91,6 +91,7 @@
|
||||||
@path-change="browserFilter = ''"
|
@path-change="browserFilter = ''"
|
||||||
@play="play($event)"
|
@play="play($event)"
|
||||||
@play-with-opts="play($event.item, $event.opts)"
|
@play-with-opts="play($event.item, $event.opts)"
|
||||||
|
@view="view"
|
||||||
v-else-if="selectedView === 'browser'"
|
v-else-if="selectedView === 'browser'"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -142,6 +143,14 @@
|
||||||
/>
|
/>
|
||||||
</Modal>
|
</Modal>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="embed-player-container" v-if="viewItem != null">
|
||||||
|
<Modal :visible="true" @close="viewItem = null">
|
||||||
|
<EmbedPlayer :item="viewItem"
|
||||||
|
:plugin-name="pluginName"
|
||||||
|
@ended="viewItem = null" />
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</keep-alive>
|
</keep-alive>
|
||||||
</template>
|
</template>
|
||||||
|
@ -151,6 +160,7 @@ import Modal from "@/components/Modal";
|
||||||
import Utils from "@/Utils";
|
import Utils from "@/Utils";
|
||||||
|
|
||||||
import Browser from "@/components/panels/Media/Browser";
|
import Browser from "@/components/panels/Media/Browser";
|
||||||
|
import EmbedPlayer from "@/components/panels/Media/EmbedPlayer";
|
||||||
import Header from "@/components/panels/Media/Header";
|
import Header from "@/components/panels/Media/Header";
|
||||||
import Info from "@/components/panels/Media/Info";
|
import Info from "@/components/panels/Media/Info";
|
||||||
import Loading from "@/components/Loading";
|
import Loading from "@/components/Loading";
|
||||||
|
@ -169,6 +179,7 @@ export default {
|
||||||
mixins: [Utils, MediaUtils],
|
mixins: [Utils, MediaUtils],
|
||||||
components: {
|
components: {
|
||||||
Browser,
|
Browser,
|
||||||
|
EmbedPlayer,
|
||||||
Header,
|
Header,
|
||||||
Info,
|
Info,
|
||||||
Loading,
|
Loading,
|
||||||
|
@ -225,6 +236,7 @@ export default {
|
||||||
'torrent': true,
|
'torrent': true,
|
||||||
},
|
},
|
||||||
urlPlay: null,
|
urlPlay: null,
|
||||||
|
viewItem: null,
|
||||||
torrentPlugin: null,
|
torrentPlugin: null,
|
||||||
torrentPlugins: [
|
torrentPlugins: [
|
||||||
'torrent',
|
'torrent',
|
||||||
|
@ -373,8 +385,7 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
async view(item) {
|
async view(item) {
|
||||||
const ret = await this.startStreaming(item, this.pluginName, true)
|
this.viewItem = item
|
||||||
window.open(ret.url, '_blank')
|
|
||||||
},
|
},
|
||||||
|
|
||||||
async download(item, args) {
|
async download(item, args) {
|
||||||
|
@ -867,4 +878,18 @@ export default {
|
||||||
padding: 0 !important;
|
padding: 0 !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:deep(.embed-player-container) {
|
||||||
|
.content {
|
||||||
|
max-width: 95%;
|
||||||
|
background: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.body {
|
||||||
|
height: 100%;
|
||||||
|
max-height: 85vh !important;
|
||||||
|
background: black;
|
||||||
|
padding: 0 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -159,7 +159,7 @@ export default {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.item.type === 'file') {
|
if (['file', 'jellyfin', 'youtube'].includes(this.item.type)) {
|
||||||
actions.push({
|
actions.push({
|
||||||
iconClass: 'fa fa-window-maximize',
|
iconClass: 'fa fa-window-maximize',
|
||||||
text: 'View in Browser',
|
text: 'View in Browser',
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
v-on="componentData.on"
|
v-on="componentData.on"
|
||||||
:collection="collection"
|
:collection="collection"
|
||||||
@select="select"
|
@select="select"
|
||||||
|
@view="$emit('view', $event)"
|
||||||
v-else-if="currentView === 'movies'" />
|
v-else-if="currentView === 'movies'" />
|
||||||
|
|
||||||
<Media v-bind="componentData.props"
|
<Media v-bind="componentData.props"
|
||||||
|
@ -16,6 +17,7 @@
|
||||||
:collection="collection"
|
:collection="collection"
|
||||||
@select="select"
|
@select="select"
|
||||||
@select-collection="selectCollection"
|
@select-collection="selectCollection"
|
||||||
|
@view="$emit('view', $event)"
|
||||||
v-else />
|
v-else />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -44,6 +46,7 @@ export default {
|
||||||
'path-change',
|
'path-change',
|
||||||
'play',
|
'play',
|
||||||
'play-with-opts',
|
'play-with-opts',
|
||||||
|
'view',
|
||||||
],
|
],
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
|
|
|
@ -11,6 +11,7 @@ export default {
|
||||||
'play',
|
'play',
|
||||||
'play-with-opts',
|
'play-with-opts',
|
||||||
'select',
|
'select',
|
||||||
|
'view',
|
||||||
],
|
],
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
|
|
|
@ -37,6 +37,7 @@
|
||||||
@play-with-opts="$emit('play-with-opts', $event)"
|
@play-with-opts="$emit('play-with-opts', $event)"
|
||||||
@remove-from-playlist="$emit('remove-from-playlist', $event)"
|
@remove-from-playlist="$emit('remove-from-playlist', $event)"
|
||||||
@select="selectedResult = $event"
|
@select="selectedResult = $event"
|
||||||
|
@view="$emit('view', $event)"
|
||||||
v-if="mediaItems.length > 0" />
|
v-if="mediaItems.length > 0" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
@play-with-opts="$emit('play-with-opts', $event)"
|
@play-with-opts="$emit('play-with-opts', $event)"
|
||||||
@remove-from-playlist="$emit('remove-from-playlist', $event)"
|
@remove-from-playlist="$emit('remove-from-playlist', $event)"
|
||||||
@select="selectedResult = $event"
|
@select="selectedResult = $event"
|
||||||
|
@view="$emit('view', $event)"
|
||||||
v-else />
|
v-else />
|
||||||
|
|
||||||
<SortButton :value="sort"
|
<SortButton :value="sort"
|
||||||
|
|
|
@ -64,6 +64,7 @@
|
||||||
@play-with-opts="$emit('play-with-opts', $event)"
|
@play-with-opts="$emit('play-with-opts', $event)"
|
||||||
@remove-from-playlist="$emit('remove-from-playlist', $event)"
|
@remove-from-playlist="$emit('remove-from-playlist', $event)"
|
||||||
@select="selectedResult = $event"
|
@select="selectedResult = $event"
|
||||||
|
@view="$emit('view', $event)"
|
||||||
v-if="mediaItems?.length > 0" />
|
v-if="mediaItems?.length > 0" />
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
@open-channel="selectChannelFromItem"
|
@open-channel="selectChannelFromItem"
|
||||||
@play="$emit('play', $event)"
|
@play="$emit('play', $event)"
|
||||||
@play-with-opts="$emit('play-with-opts', $event)"
|
@play-with-opts="$emit('play-with-opts', $event)"
|
||||||
|
@view="$emit('view', $event)"
|
||||||
v-if="selectedView === 'feed'"
|
v-if="selectedView === 'feed'"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
@ -29,6 +30,7 @@
|
||||||
@play-with-opts="$emit('play-with-opts', $event)"
|
@play-with-opts="$emit('play-with-opts', $event)"
|
||||||
@remove-from-playlist="removeFromPlaylist"
|
@remove-from-playlist="removeFromPlaylist"
|
||||||
@select="onPlaylistSelected"
|
@select="onPlaylistSelected"
|
||||||
|
@view="$emit('view', $event)"
|
||||||
v-else-if="selectedView === 'playlists'"
|
v-else-if="selectedView === 'playlists'"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
@ -41,6 +43,7 @@
|
||||||
@play="$emit('play', $event)"
|
@play="$emit('play', $event)"
|
||||||
@play-with-opts="$emit('play-with-opts', $event)"
|
@play-with-opts="$emit('play-with-opts', $event)"
|
||||||
@select="onChannelSelected"
|
@select="onChannelSelected"
|
||||||
|
@view="$emit('view', $event)"
|
||||||
v-else-if="selectedView === 'subscriptions'"
|
v-else-if="selectedView === 'subscriptions'"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
@ -80,6 +83,7 @@ export default {
|
||||||
'download-audio',
|
'download-audio',
|
||||||
'play',
|
'play',
|
||||||
'play-with-opts',
|
'play-with-opts',
|
||||||
|
'view',
|
||||||
],
|
],
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
|
|
|
@ -54,6 +54,7 @@
|
||||||
@play-with-opts="$emit('play-with-opts', $event)"
|
@play-with-opts="$emit('play-with-opts', $event)"
|
||||||
@scroll-end="loadNextPage"
|
@scroll-end="loadNextPage"
|
||||||
@select="selectedResult = $event"
|
@select="selectedResult = $event"
|
||||||
|
@view="$emit('view', $event)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -73,6 +74,7 @@ export default {
|
||||||
'open-channel',
|
'open-channel',
|
||||||
'play',
|
'play',
|
||||||
'play-with-opts',
|
'play-with-opts',
|
||||||
|
'view',
|
||||||
],
|
],
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
@select="selectedResult = $event"
|
@select="selectedResult = $event"
|
||||||
@play="$emit('play', $event)"
|
@play="$emit('play', $event)"
|
||||||
@play-with-opts="$emit('play-with-opts', $event)"
|
@play-with-opts="$emit('play-with-opts', $event)"
|
||||||
|
@view="$emit('view', $event)"
|
||||||
v-else />
|
v-else />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -35,6 +36,7 @@ export default {
|
||||||
'open-channel',
|
'open-channel',
|
||||||
'play',
|
'play',
|
||||||
'play-with-opts',
|
'play-with-opts',
|
||||||
|
'view',
|
||||||
],
|
],
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
|
|
|
@ -57,6 +57,7 @@
|
||||||
@play-with-opts="$emit('play-with-opts', $event)"
|
@play-with-opts="$emit('play-with-opts', $event)"
|
||||||
@remove-from-playlist="$emit('remove-from-playlist', $event)"
|
@remove-from-playlist="$emit('remove-from-playlist', $event)"
|
||||||
@select="selectedResult = $event"
|
@select="selectedResult = $event"
|
||||||
|
@view="$emit('view', $event)"
|
||||||
v-else />
|
v-else />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -78,6 +79,7 @@ export default {
|
||||||
'play',
|
'play',
|
||||||
'play-with-opts',
|
'play-with-opts',
|
||||||
'remove-from-playlist',
|
'remove-from-playlist',
|
||||||
|
'view',
|
||||||
],
|
],
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
|
|
Loading…
Reference in a new issue