styling, video title, disable autoplay
This commit is contained in:
parent
c54698a4aa
commit
607b845abc
2 changed files with 100 additions and 14 deletions
12
src/App.vue
12
src/App.vue
|
@ -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 {
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in a new issue