201 lines
5.3 KiB
Vue
201 lines
5.3 KiB
Vue
<template>
|
|
<div :class="['channel', `channel-${loadingState}`]">
|
|
<div class="name">{{name}}</div>
|
|
<VolumeSlider :value="volume" :disabled="loadingState === 'unloaded'"
|
|
@valueChange="(actualValue) => {actualVolume = actualValue}"/>
|
|
<div class="title">{{loadingState === "loading" ? "Loading..." : title}}</div>
|
|
<div class="youtube-player" ref="ytpl"/>
|
|
</div>
|
|
</template>
|
|
|
|
<!--suppress JSUnusedLocalSymbols -->
|
|
<script lang="ts">
|
|
import {Component, Prop, Vue, Watch} from "vue-property-decorator";
|
|
import YouTubePlayerFactory from "youtube-player";
|
|
// noinspection TypeScriptCheckImport
|
|
import {YouTubePlayer} from "youtube-player/dist/types";
|
|
import PlayerStates from "youtube-player/dist/constants/PlayerStates";
|
|
import {LoadingState, PlayingState} from "@/common";
|
|
import VolumeSlider from "@/components/VolumeSlider.vue";
|
|
|
|
enum SOURCE {
|
|
YouTube,
|
|
DIRECT
|
|
}
|
|
|
|
@Component({
|
|
components: {VolumeSlider}
|
|
})
|
|
export default class Channel extends Vue {
|
|
@Prop() public name!: string;
|
|
@Prop() public volume: number = 100;
|
|
@Prop() public url: string | undefined;
|
|
@Prop() public playing: PlayingState = PlayingState.STOPPED;
|
|
|
|
private youtubePlayer?: YouTubePlayer;
|
|
private loadingState = LoadingState.UNLOADED;
|
|
private title = "";
|
|
private actualVolume = this.volume;
|
|
|
|
private get source() {
|
|
if (this.url) {
|
|
if (this.url.includes("youtube") || true) { // TODO
|
|
return SOURCE.YouTube;
|
|
}
|
|
}
|
|
}
|
|
|
|
@Watch("playing")
|
|
private onPlayingChange() {
|
|
if (this.youtubePlayer) {
|
|
switch (this.playing) {
|
|
case PlayingState.PLAYING:
|
|
this.youtubePlayer.setVolume(this.actualVolume);
|
|
this.youtubePlayer.playVideo();
|
|
break;
|
|
case PlayingState.STOPPED:
|
|
this.youtubePlayer.stopVideo();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
@Watch("url")
|
|
private onUrlChange() {
|
|
if (this.url !== undefined && this.url.length > 0) {
|
|
if (this.youtubePlayer === undefined) {
|
|
this.youtubePlayer = YouTubePlayerFactory(this.$refs.ytpl as HTMLDivElement, {
|
|
playerVars: {"autoplay": 0},
|
|
});
|
|
this.youtubePlayer.on("stateChange", (event) => {
|
|
switch (event.data) {
|
|
case PlayerStates.BUFFERING:
|
|
this.loadingState = LoadingState.LOADED;
|
|
if (this.playing == PlayingState.PLAYING) {
|
|
this.youtubePlayer!.playVideo();
|
|
}
|
|
break;
|
|
case PlayerStates.ENDED:
|
|
this.youtubePlayer!.playVideo();
|
|
break;
|
|
}
|
|
});
|
|
}
|
|
const videoId = Channel.extractYoutubeId(this.url);
|
|
if (videoId !== undefined) {
|
|
this.loadingState = LoadingState.LOADING;
|
|
console.log(`Loading YouTube video "${videoId}"`);
|
|
this.youtubePlayer.loadVideoById(videoId);
|
|
this.title = "Loading...";
|
|
Channel.fetchYoutubeTitle(videoId).then((title) => {
|
|
this.title = title;
|
|
}).catch((error) => {
|
|
console.error(error);
|
|
this.title = "ERR";
|
|
});
|
|
} else {
|
|
console.error(`Something went wrong trying to parse ${this.url}`);
|
|
}
|
|
} else {
|
|
this.loadingState = LoadingState.UNLOADED;
|
|
this.title = "N/A";
|
|
if (this.youtubePlayer) {
|
|
this.youtubePlayer.stopVideo();
|
|
}
|
|
}
|
|
}
|
|
|
|
@Watch("actualVolume")
|
|
private onActualVolumeChange() {
|
|
if (this.youtubePlayer) {
|
|
this.youtubePlayer.setVolume(this.actualVolume);
|
|
}
|
|
}
|
|
|
|
@Watch("loadingState")
|
|
private onLoadingStateChange() {
|
|
this.$emit("loadingState", this.loadingState);
|
|
}
|
|
|
|
private mounted() {
|
|
this.onUrlChange();
|
|
}
|
|
|
|
private static extractYoutubeId(url: string): string | undefined {
|
|
const videoIdMatch = url.match(/(v=|youtu\.be\/)([^&]+)/);
|
|
if (videoIdMatch !== null && videoIdMatch.length == 3) {
|
|
return videoIdMatch[2];
|
|
}
|
|
}
|
|
|
|
private static fetchYoutubeTitle(videoId: string): Promise<string> {
|
|
return new Promise((resolve, reject) => {
|
|
let url = `https://www.googleapis.com/youtube/v3/videos?part=snippet&id=${videoId}`;
|
|
if (process.env.VUE_APP_GOOGLE_API_KEY) {
|
|
url += `&key=${process.env.VUE_APP_GOOGLE_API_KEY}`;
|
|
} else {
|
|
console.warn("Using unauthenticated Google API...");
|
|
}
|
|
fetch(url).then((resp) => {
|
|
resp.json().then((jsonResp) => {
|
|
if (jsonResp["error"]) {
|
|
reject(jsonResp["error"]);
|
|
} else {
|
|
console.debug(jsonResp);
|
|
resolve(jsonResp.items[0].snippet.title);
|
|
}
|
|
}).catch((error) => {
|
|
reject(error);
|
|
});
|
|
}).catch((error) => {
|
|
reject(error);
|
|
});
|
|
});
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<!--suppress CssUnusedSymbol -->
|
|
<style scoped>
|
|
.channel {
|
|
width: 128px;
|
|
height: 256px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
font-size: 10px;
|
|
border: 1px solid black;
|
|
}
|
|
|
|
.channel-loading {
|
|
background: grey;
|
|
}
|
|
|
|
.channel-unloaded {
|
|
background: lightgray;
|
|
}
|
|
|
|
.name {
|
|
text-align: center;
|
|
font-weight: bold;
|
|
padding: 2px 0;
|
|
}
|
|
|
|
.title {
|
|
margin: .25em 1em;
|
|
display: -webkit-box;
|
|
/*noinspection CssUnknownProperty*/
|
|
-webkit-line-clamp: 2;
|
|
-webkit-box-orient: vertical;
|
|
overflow: hidden;
|
|
text-align: left;
|
|
}
|
|
</style>
|
|
|
|
<style>
|
|
.youtube-player {
|
|
display: none;
|
|
}
|
|
</style>
|