urls can be edited while playing; refactoring
state split into loading and playing playing state is global radonly defaultmedia, remove useless getter, reorder fns
This commit is contained in:
parent
59c3c3381c
commit
3ae972d09e
3 changed files with 68 additions and 42 deletions
24
src/App.vue
24
src/App.vue
|
@ -1,3 +1,4 @@
|
|||
import {PlayingState} from "@/common";
|
||||
<!--suppress HtmlFormInputWithoutLabel -->
|
||||
<template>
|
||||
<div id="app">
|
||||
|
@ -5,8 +6,8 @@
|
|||
<div class="channels-wrapper">
|
||||
<div class="channels">
|
||||
<template v-for="i in N_CHANNELS">
|
||||
<Channel :name="names[i - 1]" :url="urls[i - 1]" :volume="volumes[i-1]"
|
||||
:key="i"
|
||||
<Channel :name="names[i - 1]" :url="urls[i - 1]" :volume="volumes[i-1]" :playing="playing"
|
||||
:key="i" @loadingState="(state) => {$set(states, i - 1, state)}"
|
||||
class="channel" ref="channels"/>
|
||||
</template>
|
||||
</div>
|
||||
|
@ -20,7 +21,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="controls">
|
||||
<button @click="start">START</button>
|
||||
<button @click="start" :disabled="!startEnabled">START</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -29,6 +30,7 @@
|
|||
<script lang="ts">
|
||||
import {Component, Vue} from "vue-property-decorator";
|
||||
import Channel from "@/components/Channel.vue";
|
||||
import {LoadingState, PlayingState} from "@/common";
|
||||
|
||||
@Component({
|
||||
components: {
|
||||
|
@ -41,18 +43,22 @@ export default class App extends Vue {
|
|||
private readonly LFO_DEPTH = 33;
|
||||
private readonly LFO_OFFSET = 66;
|
||||
|
||||
private playing: PlayingState = PlayingState.STOPPED;
|
||||
private names = ["alpha", "beta", "gamma", "delta", "epsilon", "zeta", "eta", "theta", "iota", "kappa", "lambda", "mu"];
|
||||
private urls = Array(this.N_CHANNELS).fill("");
|
||||
private volumes = Array(this.N_CHANNELS).fill(50);
|
||||
private states = Array(this.N_CHANNELS).fill(LoadingState.UNLOADED);
|
||||
|
||||
private animateVolumeStart?: Date;
|
||||
private animateVolumeInterval?: number;
|
||||
|
||||
private defaultMedia = [
|
||||
private readonly defaultMedia = [
|
||||
"https://www.youtube.com/watch?v=jX6kn9_U8qk",
|
||||
"https://www.youtube.com/watch?v=E77jmtut1Zc",
|
||||
"https://youtu.be/OW7TH2U4hps"
|
||||
];
|
||||
|
||||
|
||||
private mounted() {
|
||||
this.defaultMedia.forEach((url, idx) => {
|
||||
this.$set(this.urls, idx, url);
|
||||
|
@ -60,10 +66,8 @@ export default class App extends Vue {
|
|||
}
|
||||
|
||||
private start() {
|
||||
(this.$refs.channels as Channel[]).forEach((channel) => {
|
||||
channel.start();
|
||||
this.animateVolume();
|
||||
});
|
||||
this.playing = PlayingState.PLAYING;
|
||||
this.animateVolume();
|
||||
}
|
||||
|
||||
private animateVolume() {
|
||||
|
@ -82,6 +86,10 @@ export default class App extends Vue {
|
|||
}
|
||||
}, 1000 / 60);
|
||||
}
|
||||
|
||||
private get startEnabled() {
|
||||
return this.states.every((state) => state !== LoadingState.LOADING);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
12
src/common.ts
Normal file
12
src/common.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
enum LoadingState {
|
||||
UNLOADED = "unloaded",
|
||||
LOADING = "loading",
|
||||
LOADED = "loaded",
|
||||
}
|
||||
|
||||
enum PlayingState {
|
||||
STOPPED = "stopped",
|
||||
PLAYING = "playing"
|
||||
}
|
||||
|
||||
export {LoadingState, PlayingState};
|
|
@ -1,13 +1,13 @@
|
|||
<template>
|
||||
<div :class="['channel', `channel-${state}`]">
|
||||
<div :class="['channel', `channel-${loadingState}`]">
|
||||
<div class="volume-wrapper">
|
||||
<!--suppress HtmlFormInputWithoutLabel -->
|
||||
<input class="volume" :disabled="state === 'unloaded'"
|
||||
<input class="volume" :disabled="loadingState === 'unloaded'"
|
||||
type="range" min="0" max="100" v-model="volume"/>
|
||||
</div>
|
||||
<div class="description">
|
||||
<div class="name">{{name}}</div>
|
||||
<div class="title">{{state === "loading" ? "Loading..." : title}}</div>
|
||||
<div class="title">{{loadingState === "loading" ? "Loading..." : title}}</div>
|
||||
</div>
|
||||
<div class="youtube-player" ref="ytpl"/>
|
||||
</div>
|
||||
|
@ -20,26 +20,22 @@ 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";
|
||||
|
||||
enum SOURCE {
|
||||
YouTube,
|
||||
DIRECT
|
||||
}
|
||||
|
||||
enum ChannelState {
|
||||
UNLOADED = "unloaded",
|
||||
LOADING = "loading",
|
||||
READY = "ready",
|
||||
PLAYING = "playing"
|
||||
}
|
||||
|
||||
@Component
|
||||
export default class Channel extends Vue {
|
||||
@Prop() public name: string = "Channel";
|
||||
@Prop() public volume: number = 50;
|
||||
@Prop() public url: string | undefined;
|
||||
@Prop() public playing: PlayingState = PlayingState.STOPPED;
|
||||
|
||||
private youtubePlayer?: YouTubePlayer;
|
||||
private state = ChannelState.UNLOADED;
|
||||
private loadingState = LoadingState.UNLOADED;
|
||||
private title = "";
|
||||
|
||||
private get source() {
|
||||
|
@ -50,20 +46,18 @@ 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() {
|
||||
@Watch("playing")
|
||||
private onPlayingChange() {
|
||||
if (this.youtubePlayer) {
|
||||
this.youtubePlayer.setVolume(this.volume);
|
||||
this.youtubePlayer.playVideo();
|
||||
this.state = ChannelState.PLAYING;
|
||||
switch (this.playing) {
|
||||
case PlayingState.PLAYING:
|
||||
this.youtubePlayer.setVolume(this.volume);
|
||||
this.youtubePlayer.playVideo();
|
||||
break;
|
||||
case PlayingState.STOPPED:
|
||||
this.youtubePlayer.stopVideo();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -76,8 +70,11 @@ export default class Channel extends Vue {
|
|||
});
|
||||
this.youtubePlayer.on("stateChange", (event) => {
|
||||
switch (event.data) {
|
||||
case PlayerStates.BUFFERING:
|
||||
this.state = ChannelState.READY;
|
||||
case PlayerStates.BUFFERING:
|
||||
this.loadingState = LoadingState.LOADED;
|
||||
if (this.playing == PlayingState.PLAYING) {
|
||||
this.youtubePlayer!.playVideo();
|
||||
}
|
||||
break;
|
||||
case PlayerStates.ENDED:
|
||||
this.youtubePlayer!.playVideo();
|
||||
|
@ -87,7 +84,7 @@ export default class Channel extends Vue {
|
|||
}
|
||||
const videoId = Channel.extractYoutubeId(this.url);
|
||||
if (videoId !== undefined) {
|
||||
this.state = ChannelState.LOADING;
|
||||
this.loadingState = LoadingState.LOADING;
|
||||
console.log(`Loading YouTube video "${videoId}"`);
|
||||
this.youtubePlayer.loadVideoById(videoId);
|
||||
this.title = "Loading...";
|
||||
|
@ -101,14 +98,11 @@ export default class Channel extends Vue {
|
|||
console.error(`Something went wrong trying to parse ${this.url}`);
|
||||
}
|
||||
} else {
|
||||
this.loadingState = LoadingState.UNLOADED;
|
||||
this.title = "N/A";
|
||||
}
|
||||
}
|
||||
|
||||
private static extractYoutubeId(url: string): string | undefined {
|
||||
const videoIdMatch = url.match(/(v=|youtu\.be\/)([^&]+)/);
|
||||
if (videoIdMatch !== null && videoIdMatch.length == 3) {
|
||||
return videoIdMatch[2];
|
||||
if (this.youtubePlayer) {
|
||||
this.youtubePlayer.stopVideo();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -119,10 +113,22 @@ export default class Channel extends Vue {
|
|||
}
|
||||
}
|
||||
|
||||
@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}`;
|
||||
|
|
Loading…
Reference in a new issue