0
Fork 0
mirror of https://codeberg.org/SafeTwitch/safetwitch.git synced 2024-12-22 13:22:58 -05:00

Audio-only support and invidious style selector 🥳 #25

This commit is contained in:
dragongoose 2023-08-21 15:30:18 -04:00
parent a754961a14
commit 3e6cd185ae
No known key found for this signature in database
GPG key ID: 01397EEC371CDAA5
3 changed files with 85 additions and 4 deletions

View file

@ -0,0 +1,61 @@
<template>
<div>
<audio ref="videoPlayer" class="w-full" controls></audio>
</div>
</template>
<script lang="ts">
// Importing video-js
import Hls from 'hls.js'
export default {
name: 'VideoJsPlayer',
props: {
masterManifestUrl: {
type: String,
default() {
return {}
}
}
},
emits: ['PlayerTimeUpdate'],
async setup(props) {
let player: any
const getAudioOnlyManifestFromUrl = async (masterManifestUrl: string) => {
const manifestRes = await fetch(masterManifestUrl)
if (!manifestRes.ok) return;
const manifest = await manifestRes.text()
// The last line of the manifest is the
// audio only manifest. This is a bit hacky
// but it'll work. If issues arise we can
// always implement an actual m3u8 parser
const tmp = manifest.split("\n")
const audioOnlyManifest = tmp[tmp.length - 1]
return audioOnlyManifest
}
const audioOnlyManifest = await getAudioOnlyManifestFromUrl(props.masterManifestUrl)
return {
player,
audioOnlyManifest
}
},
// initializing the video player
// when the component is being mounted
mounted() {
const video = this.$refs.videoPlayer as HTMLVideoElement;
if (Hls.isSupported()) {
const hls = new Hls();
hls.loadSource(this.audioOnlyManifest || "");
hls.attachMedia(video);
hls.on(Hls.Events.MANIFEST_PARSED, () => {
video.play();
});
}
}
}
</script>

View file

@ -22,6 +22,8 @@ app.provide('wsLink', `${wsProtocol}${import.meta.env.SAFETWITCH_BACKEND_DOMAIN}
import { OhVueIcon, addIcons } from 'oh-vue-icons' import { OhVueIcon, addIcons } from 'oh-vue-icons'
import { import {
IoSearchOutline, IoSearchOutline,
BiHeadphones,
BiCameraVideoFill,
IoLink, IoLink,
FaCircleNotch, FaCircleNotch,
BiTwitter, BiTwitter,
@ -35,6 +37,8 @@ import {
addIcons( addIcons(
IoSearchOutline, IoSearchOutline,
BiHeadphones,
BiCameraVideoFill,
IoLink, IoLink,
FaCircleNotch, FaCircleNotch,
BiTwitter, BiTwitter,

View file

@ -8,6 +8,7 @@ import ErrorMessage from '@/components/ErrorMessage.vue'
import FollowButton from '@/components/FollowButton.vue' import FollowButton from '@/components/FollowButton.vue'
import LoadingScreen from '@/components/LoadingScreen.vue' import LoadingScreen from '@/components/LoadingScreen.vue'
import VideoTab from '@/components/user/VideoTab.vue' import VideoTab from '@/components/user/VideoTab.vue'
import AudioPlayer from '@/components/AudioPlayer.vue'
import type { StreamerData } from '@/types' import type { StreamerData } from '@/types'
import { truncate, abbreviate, getEndpoint } from '@/mixins' import { truncate, abbreviate, getEndpoint } from '@/mixins'
@ -32,11 +33,13 @@ export default {
], ],
fluid: true fluid: true
} }
const audioOptions = `${rootBackendUrl}/proxy/stream/${username}/hls.m3u8`
return { return {
data, data,
status, status,
videoOptions videoOptions,
audioOptions
} }
}, },
async mounted() { async mounted() {
@ -56,7 +59,8 @@ export default {
ErrorMessage, ErrorMessage,
FollowButton, FollowButton,
LoadingScreen, LoadingScreen,
VideoTab VideoTab,
AudioPlayer
}, },
methods: { methods: {
truncate, truncate,
@ -78,7 +82,8 @@ export default {
class="flex bg-ctp-crust flex-col p-6 rounded-lg w-[99vw] md:max-w-prose md:min-w-[65ch] lg:max-w-[70rem] text-white" class="flex bg-ctp-crust flex-col p-6 rounded-lg w-[99vw] md:max-w-prose md:min-w-[65ch] lg:max-w-[70rem] text-white"
> >
<div v-if="data.isLive" class="w-full mx-auto rounded-lg mb-5"> <div v-if="data.isLive" class="w-full mx-auto rounded-lg mb-5">
<video-player :options="videoOptions"> </video-player> <video-player v-if="Boolean($route.query['audio-only']) === false" :options="videoOptions"> </video-player>
<audio-player v-else :masterManifestUrl="audioOptions"></audio-player>
</div> </div>
<img <img
@ -105,7 +110,18 @@ export default {
</div> </div>
<div class="ml-3 content-between"> <div class="ml-3 content-between">
<h1 class="text-2xl md:text-4xl font-bold">{{ data.username }}</h1> <div>
<h1 class="text-2xl md:text-4xl font-bold inline-block">{{ data.username }}</h1>
<router-link v-if="$route.query['audio-only'] !== 'true'" to="?audio-only=true">
<v-icon name="bi-headphones" class="ml-1 w-8 h-8 inline-block"></v-icon>
</router-link>
<!-- For some reason it doesn't like going from
audio only to video, so we'll have the page reload
-->
<a v-else :href="$route.params.username.toString()">
<v-icon name="bi-camera-video-fill" class="ml-1 w-8 h-8 inline-block"></v-icon>
</a>
</div>
<div v-if="data.stream" class="w-[14rem] md:w-[17rem]"> <div v-if="data.stream" class="w-[14rem] md:w-[17rem]">
<p class="text-sm font-bold text-gray-200 self-end"> <p class="text-sm font-bold text-gray-200 self-end">
{{ truncate(data.stream.title, 130) }} {{ truncate(data.stream.title, 130) }}