styling, video title, disable autoplay

This commit is contained in:
Tomáš Mládek 2020-01-10 16:11:49 +01:00
parent c54698a4aa
commit 607b845abc
2 changed files with 100 additions and 14 deletions

View file

@ -35,8 +35,20 @@ export default class App extends Vue {
</script> </script>
<style> <style>
html, body {
padding: 0;
margin: 0;
}
#app {
display: flex;
flex-direction: column;
align-items: center;
}
.channels { .channels {
display: flex; display: flex;
padding: 2rem;
} }
.channel { .channel {

View file

@ -1,8 +1,11 @@
<template> <template>
<div :class="['channel', `channel-${state}`]"> <div :class="['channel', `channel-${state}`]">
<div class="volume-wrapper">
<!--suppress HtmlFormInputWithoutLabel --> <!--suppress HtmlFormInputWithoutLabel -->
<input class="volume" :disabled="state === 'unloaded'" <input class="volume" :disabled="state === 'unloaded'"
type="range" min="0" max="100" v-model="volume"/> type="range" min="0" max="100" v-model="volume"/>
</div>
<div class="title">{{state === "loading" ? "Loading..." : title}}</div>
<div class="youtube-player" ref="ytpl"/> <div class="youtube-player" ref="ytpl"/>
</div> </div>
</template> </template>
@ -34,6 +37,7 @@ export default class Channel extends Vue {
private animateVolumeStart?: Date; private animateVolumeStart?: Date;
private animateVolumeInterval?: number; private animateVolumeInterval?: number;
private state = ChannelState.UNLOADED; private state = ChannelState.UNLOADED;
private title = "";
private volume: number = 50; private volume: number = 50;
@ -45,6 +49,15 @@ export default class Channel extends Vue {
} }
} }
private get ytVideoId() {
if (this.url) {
const match = this.url.match(/v=([\w_\-]+)/);
if (match !== null && match.length == 2) {
return match[1];
}
}
}
public start() { public start() {
if (this.youtubePlayer) { if (this.youtubePlayer) {
this.youtubePlayer.setVolume(this.volume); this.youtubePlayer.setVolume(this.volume);
@ -58,21 +71,33 @@ export default class Channel extends Vue {
private onUrlChange() { private onUrlChange() {
if (this.url !== undefined) { if (this.url !== undefined) {
if (this.youtubePlayer === undefined) { if (this.youtubePlayer === undefined) {
this.youtubePlayer = YouTubePlayerFactory(this.$refs.ytpl as HTMLDivElement); this.youtubePlayer = YouTubePlayerFactory(this.$refs.ytpl as HTMLDivElement, {
playerVars: {"autoplay": 0},
});
this.youtubePlayer.on("stateChange", (event) => { this.youtubePlayer.on("stateChange", (event) => {
if (event.data == PlayerStates.BUFFERING) { if (event.data == PlayerStates.BUFFERING) {
this.state = ChannelState.READY; this.state = ChannelState.READY;
} }
}); });
} }
const videoId = this.url.match(/v=([\w_\-]+)/); const videoIdMatch = this.url.match(/v=([\w_\-]+)/);
if (videoId !== null && videoId.length == 2) { if (videoIdMatch !== null && videoIdMatch.length == 2) {
const videoId = videoIdMatch[1];
this.state = ChannelState.LOADING; this.state = ChannelState.LOADING;
console.log(`Loading YouTube video "${videoId[1]}"`); console.log(`Loading YouTube video "${videoId}"`);
this.youtubePlayer.loadVideoById(videoId[1]); this.youtubePlayer.loadVideoById(videoId);
this.title = "Loading...";
Channel.fetchYoutubeTitle(videoId).then((title) => {
this.title = title;
}).catch((error) => {
console.error(error);
this.title = "ERR";
});
} else { } else {
console.error(`Something went wrong trying to parse ${this.url}`); console.error(`Something went wrong trying to parse ${this.url}`);
} }
} else {
this.title = "N/A";
} }
} }
@ -98,15 +123,46 @@ export default class Channel extends Vue {
} }
}, 1000 / 60); }, 1000 / 60);
} }
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> </script>
<!--suppress CssUnusedSymbol --> <!--suppress CssUnusedSymbol -->
<style scoped> <style scoped>
.channel { .channel {
width: 128px;
height: 256px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
font-family: monospace;
font-size: 8px;
border: 1px solid black; border: 1px solid black;
width: 20px;
height: 170px;
} }
.channel-loading { .channel-loading {
@ -117,14 +173,32 @@ export default class Channel extends Vue {
background: lightgray; background: lightgray;
} }
.volume-wrapper {
--height: 200px;
--width: 30px;
height: var(--height);
width: var(--width);
display: block;
}
.volume { .volume {
position: relative; position: relative;
width: 150px; width: var(--height);
height: 15px; height: var(--width);
top: calc(150px / 2); top: calc(var(--height) / 2);
left: calc(150px / 2 * -1 + 15px / 2); left: calc(var(--height) / 2 * -1 + var(--width) / 2);
transform: rotate(270deg); transform: rotate(270deg);
} }
.title {
margin: .25em 1em;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
text-align: left;
}
</style> </style>
<style> <style>