[#414] Added embedded player support for Jellyfin and YouTube media.

This commit is contained in:
Fabio Manganiello 2024-10-19 17:07:05 +02:00
parent 4015cf356d
commit d171795e7c
Signed by untrusted user: blacklight
GPG key ID: D90FBA7F76362774
13 changed files with 205 additions and 3 deletions

View file

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

View file

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

View file

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

View file

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

View file

@ -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() {

View file

@ -11,6 +11,7 @@ export default {
'play', 'play',
'play-with-opts', 'play-with-opts',
'select', 'select',
'view',
], ],
props: { props: {

View file

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

View file

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

View file

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

View file

@ -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() {

View file

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

View file

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

View file

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