Compare commits
No commits in common. "master" and "v0.1.0" have entirely different histories.
8 changed files with 42 additions and 1033 deletions
36
CHANGELOG.md
36
CHANGELOG.md
|
@ -1,36 +0,0 @@
|
||||||
# Changelog
|
|
||||||
All notable changes to this project will be documented in this file.
|
|
||||||
|
|
||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
||||||
|
|
||||||
## [Unreleased]
|
|
||||||
|
|
||||||
## [0.1.3] - 2020-01-12
|
|
||||||
### Fixed
|
|
||||||
- Fixed volume actually not getting animated after first slider movement.
|
|
||||||
|
|
||||||
## [0.1.2] - 2020-01-12
|
|
||||||
### Added
|
|
||||||
- Slider styling
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
- Slider now controls *maximum* volume independent of animated volume (i.e. acts as a volume fader)
|
|
||||||
|
|
||||||
## [0.1.1] - 2020-01-12
|
|
||||||
### Added
|
|
||||||
- Clear button for URLs.
|
|
||||||
- Basic layout responsiveness.
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
- Move channel title to top.
|
|
||||||
|
|
||||||
## [0.1.0] - 2020-01-11
|
|
||||||
### Added
|
|
||||||
- First usable version - single animation mode yet; YouTube only.
|
|
||||||
|
|
||||||
[Unreleased]: https://gitlab.com/tmladek/noise-maker/compare/v0.1.3...master
|
|
||||||
[0.1.3]: https://gitlab.com/tmladek/noise-maker/compare/v0.1.2...v0.1.3
|
|
||||||
[0.1.2]: https://gitlab.com/tmladek/noise-maker/compare/v0.1.1...v0.1.2
|
|
||||||
[0.1.1]: https://gitlab.com/tmladek/noise-maker/compare/v0.1.0...v0.1.1
|
|
||||||
[0.1.0]: https://gitlab.com/tmladek/noise-maker/-/tags/v0.1.0
|
|
25
LICENSE
25
LICENSE
|
@ -1,25 +0,0 @@
|
||||||
BSD 2-Clause License
|
|
||||||
|
|
||||||
Copyright (c) 2020, Tomáš Mládek
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are met:
|
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright notice, this
|
|
||||||
list of conditions and the following disclaimer.
|
|
||||||
|
|
||||||
* Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer in the documentation
|
|
||||||
and/or other materials provided with the distribution.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
||||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
806
package-lock.json
generated
806
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "noisemaker",
|
"name": "noisemaker",
|
||||||
"version": "0.1.3",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"serve": "vue-cli-service serve",
|
"serve": "vue-cli-service serve",
|
||||||
|
@ -14,12 +14,10 @@
|
||||||
"youtube-player": "^5.5.2"
|
"youtube-player": "^5.5.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/youtube-player": "^5.5.1",
|
|
||||||
"@vue/cli-plugin-babel": "^4.1.0",
|
"@vue/cli-plugin-babel": "^4.1.0",
|
||||||
"@vue/cli-plugin-typescript": "^4.1.0",
|
"@vue/cli-plugin-typescript": "^4.1.0",
|
||||||
"@vue/cli-service": "^4.1.0",
|
"@vue/cli-service": "^4.1.0",
|
||||||
"node-sass": "^4.13.0",
|
"@types/youtube-player": "^5.5.1",
|
||||||
"sass-loader": "^8.0.1",
|
|
||||||
"typescript": "~3.5.3",
|
"typescript": "~3.5.3",
|
||||||
"vue-template-compiler": "^2.6.10"
|
"vue-template-compiler": "^2.6.10"
|
||||||
}
|
}
|
||||||
|
|
16
src/App.vue
16
src/App.vue
|
@ -18,7 +18,6 @@ import {PlayingState} from "@/common";
|
||||||
</label>
|
</label>
|
||||||
<input :id="`url-input-${names[i-1]}`" type="text" v-model="urls[i - 1]"/>
|
<input :id="`url-input-${names[i-1]}`" type="text" v-model="urls[i - 1]"/>
|
||||||
</div>
|
</div>
|
||||||
<button class="channel-urls-clear-button" @click="urls = Array(N_CHANNELS).fill('')">CLEAR</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
|
@ -47,7 +46,7 @@ export default class App extends Vue {
|
||||||
private playing: PlayingState = PlayingState.STOPPED;
|
private playing: PlayingState = PlayingState.STOPPED;
|
||||||
private names = ["alpha", "beta", "gamma", "delta", "epsilon", "zeta", "eta", "theta", "iota", "kappa", "lambda", "mu"];
|
private names = ["alpha", "beta", "gamma", "delta", "epsilon", "zeta", "eta", "theta", "iota", "kappa", "lambda", "mu"];
|
||||||
private urls = Array(this.N_CHANNELS).fill("");
|
private urls = Array(this.N_CHANNELS).fill("");
|
||||||
private volumes = Array(this.N_CHANNELS).fill(100);
|
private volumes = Array(this.N_CHANNELS).fill(50);
|
||||||
private states = Array(this.N_CHANNELS).fill(LoadingState.UNLOADED);
|
private states = Array(this.N_CHANNELS).fill(LoadingState.UNLOADED);
|
||||||
|
|
||||||
private animateVolumeStart?: Date;
|
private animateVolumeStart?: Date;
|
||||||
|
@ -134,14 +133,11 @@ h1 {
|
||||||
.channels-wrapper {
|
.channels-wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 80%;
|
width: 80%;
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.channels {
|
.channels {
|
||||||
display: flex;
|
display: flex;
|
||||||
padding: 0 1rem 1rem 0;
|
padding: 0 1rem;
|
||||||
justify-content: space-between;
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.channel {
|
.channel {
|
||||||
|
@ -151,20 +147,14 @@ h1 {
|
||||||
.channel-urls {
|
.channel-urls {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
flex-grow: 1;
|
width: 100%;
|
||||||
}
|
|
||||||
|
|
||||||
.channel-urls-clear-button {
|
|
||||||
max-width: 5em;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.url-input {
|
.url-input {
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin: .2rem 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.url-input label {
|
.url-input label {
|
||||||
|
|
BIN
src/assets/logo.png
Normal file
BIN
src/assets/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.7 KiB |
|
@ -1,9 +1,14 @@
|
||||||
<template>
|
<template>
|
||||||
<div :class="['channel', `channel-${loadingState}`]">
|
<div :class="['channel', `channel-${loadingState}`]">
|
||||||
<div class="name">{{name}}</div>
|
<div class="volume-wrapper">
|
||||||
<VolumeSlider :value="volume" :disabled="loadingState === 'unloaded'"
|
<!--suppress HtmlFormInputWithoutLabel -->
|
||||||
@valueChange="(actualValue) => {actualVolume = actualValue}"/>
|
<input class="volume" :disabled="loadingState === 'unloaded'"
|
||||||
<div class="title">{{loadingState === "loading" ? "Loading..." : title}}</div>
|
type="range" min="0" max="100" v-model="volume"/>
|
||||||
|
</div>
|
||||||
|
<div class="description">
|
||||||
|
<div class="name">{{name}}</div>
|
||||||
|
<div class="title">{{loadingState === "loading" ? "Loading..." : title}}</div>
|
||||||
|
</div>
|
||||||
<div class="youtube-player" ref="ytpl"/>
|
<div class="youtube-player" ref="ytpl"/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -16,26 +21,22 @@ import YouTubePlayerFactory from "youtube-player";
|
||||||
import {YouTubePlayer} from "youtube-player/dist/types";
|
import {YouTubePlayer} from "youtube-player/dist/types";
|
||||||
import PlayerStates from "youtube-player/dist/constants/PlayerStates";
|
import PlayerStates from "youtube-player/dist/constants/PlayerStates";
|
||||||
import {LoadingState, PlayingState} from "@/common";
|
import {LoadingState, PlayingState} from "@/common";
|
||||||
import VolumeSlider from "@/components/VolumeSlider.vue";
|
|
||||||
|
|
||||||
enum SOURCE {
|
enum SOURCE {
|
||||||
YouTube,
|
YouTube,
|
||||||
DIRECT
|
DIRECT
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component
|
||||||
components: {VolumeSlider}
|
|
||||||
})
|
|
||||||
export default class Channel extends Vue {
|
export default class Channel extends Vue {
|
||||||
@Prop() public name!: string;
|
@Prop() public name: string = "Channel";
|
||||||
@Prop() public volume: number = 100;
|
@Prop() public volume: number = 50;
|
||||||
@Prop() public url: string | undefined;
|
@Prop() public url: string | undefined;
|
||||||
@Prop() public playing: PlayingState = PlayingState.STOPPED;
|
@Prop() public playing: PlayingState = PlayingState.STOPPED;
|
||||||
|
|
||||||
private youtubePlayer?: YouTubePlayer;
|
private youtubePlayer?: YouTubePlayer;
|
||||||
private loadingState = LoadingState.UNLOADED;
|
private loadingState = LoadingState.UNLOADED;
|
||||||
private title = "";
|
private title = "";
|
||||||
private actualVolume = this.volume;
|
|
||||||
|
|
||||||
private get source() {
|
private get source() {
|
||||||
if (this.url) {
|
if (this.url) {
|
||||||
|
@ -50,7 +51,7 @@ export default class Channel extends Vue {
|
||||||
if (this.youtubePlayer) {
|
if (this.youtubePlayer) {
|
||||||
switch (this.playing) {
|
switch (this.playing) {
|
||||||
case PlayingState.PLAYING:
|
case PlayingState.PLAYING:
|
||||||
this.youtubePlayer.setVolume(this.actualVolume);
|
this.youtubePlayer.setVolume(this.volume);
|
||||||
this.youtubePlayer.playVideo();
|
this.youtubePlayer.playVideo();
|
||||||
break;
|
break;
|
||||||
case PlayingState.STOPPED:
|
case PlayingState.STOPPED:
|
||||||
|
@ -105,10 +106,10 @@ export default class Channel extends Vue {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Watch("actualVolume")
|
@Watch("volume")
|
||||||
private onActualVolumeChange() {
|
private onVolumeChange() {
|
||||||
if (this.youtubePlayer) {
|
if (this.youtubePlayer) {
|
||||||
this.youtubePlayer.setVolume(this.actualVolume);
|
this.youtubePlayer.setVolume(this.volume);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -176,10 +177,27 @@ export default class Channel extends Vue {
|
||||||
background: lightgray;
|
background: lightgray;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.volume-wrapper {
|
||||||
|
--height: 180px;
|
||||||
|
--width: 30px;
|
||||||
|
height: var(--height);
|
||||||
|
width: var(--width);
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.volume {
|
||||||
|
position: relative;
|
||||||
|
width: var(--height);
|
||||||
|
height: var(--width);
|
||||||
|
top: calc(var(--height) / 2);
|
||||||
|
left: calc(var(--height) / 2 * -1 + var(--width) / 2);
|
||||||
|
transform: rotate(270deg);
|
||||||
|
}
|
||||||
|
|
||||||
.name {
|
.name {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
padding: 2px 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
|
|
|
@ -1,136 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="volume-wrapper">
|
|
||||||
<!--suppress HtmlFormInputWithoutLabel -->
|
|
||||||
<input class="volume" :disabled="disabled"
|
|
||||||
type="range" min="0" max="100" v-model="maxValue"
|
|
||||||
:style="`--value: ${value}%; --max-value: ${maxValue}%; --actual-value: ${value * (maxValue / 100)}%`"/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import {Component, Prop, Vue, Watch} from "vue-property-decorator";
|
|
||||||
|
|
||||||
@Component
|
|
||||||
export default class Channel extends Vue {
|
|
||||||
private name = "VolumeSlider";
|
|
||||||
|
|
||||||
private maxValue = 80;
|
|
||||||
@Prop() public value!: number;
|
|
||||||
@Prop() public disabled!: boolean;
|
|
||||||
|
|
||||||
private mounted() {
|
|
||||||
this.onMaxValueChange();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Watch("value")
|
|
||||||
@Watch("maxValue")
|
|
||||||
private onMaxValueChange() {
|
|
||||||
this.$emit("valueChange", this.value * (this.maxValue / 100), this.maxValue);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.volume-wrapper {
|
|
||||||
--height: 180px;
|
|
||||||
--width: 30px;
|
|
||||||
height: var(--height);
|
|
||||||
width: var(--width);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.volume {
|
|
||||||
position: relative;
|
|
||||||
width: var(--height);
|
|
||||||
min-width: var(--height);
|
|
||||||
height: var(--width);
|
|
||||||
min-height: var(--width);
|
|
||||||
left: calc(var(--height) / 2 * -1 + var(--width) / 2);
|
|
||||||
transform: rotate(270deg);
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type=range] {
|
|
||||||
-webkit-appearance: none;
|
|
||||||
background: transparent;
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@mixin range-track() {
|
|
||||||
height: 10px;
|
|
||||||
cursor: pointer;
|
|
||||||
background: linear-gradient(90deg,
|
|
||||||
black 0,
|
|
||||||
black var(--actual-value), white var(--actual-value),
|
|
||||||
white var(--max-value), transparent var(--max-value), transparent 100%);
|
|
||||||
border: 1px solid #010101;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@mixin range-thumb {
|
|
||||||
border: 1px solid #000000;
|
|
||||||
height: 24px;
|
|
||||||
width: 12px;
|
|
||||||
background: #ffffff;
|
|
||||||
border-radius: 0;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type=range]::-webkit-slider-runnable-track {
|
|
||||||
@include range-track;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type=range]::-moz-range-track {
|
|
||||||
@include range-track;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type=range]::-webkit-slider-thumb {
|
|
||||||
@include range-thumb;
|
|
||||||
-webkit-appearance: none;
|
|
||||||
margin-top: -8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type=range]::-moz-range-thumb {
|
|
||||||
@include range-thumb;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
input[type=range]::-ms-track {
|
|
||||||
width: 100%;
|
|
||||||
height: 8.4px;
|
|
||||||
cursor: pointer;
|
|
||||||
background: transparent;
|
|
||||||
border-color: transparent;
|
|
||||||
border-width: 16px 0;
|
|
||||||
color: transparent;
|
|
||||||
}
|
|
||||||
input[type=range]::-ms-fill-lower {
|
|
||||||
background: #2a6495;
|
|
||||||
border: 0.2px solid #010101;
|
|
||||||
border-radius: 2.6px;
|
|
||||||
}
|
|
||||||
input[type=range]::-ms-fill-upper {
|
|
||||||
background: #3071a9;
|
|
||||||
border: 0.2px solid #010101;
|
|
||||||
border-radius: 2.6px;
|
|
||||||
}
|
|
||||||
input[type=range]::-ms-thumb {
|
|
||||||
border: 1px solid #000000;
|
|
||||||
height: 36px;
|
|
||||||
width: 16px;
|
|
||||||
border-radius: 3px;
|
|
||||||
background: #ffffff;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
input[type=range]:focus::-ms-fill-lower {
|
|
||||||
background: #3071a9;
|
|
||||||
}
|
|
||||||
input[type=range]:focus::-ms-fill-upper {
|
|
||||||
background: #367ebd;
|
|
||||||
}*/
|
|
||||||
|
|
||||||
</style>
|
|
Loading…
Add table
Reference in a new issue