Skip to content

Instantly share code, notes, and snippets.

@dasl-
Created September 30, 2019 05:49
Show Gist options
  • Save dasl-/a3cc39fb1bc1498ce72d3257ea9a7e81 to your computer and use it in GitHub Desktop.
Save dasl-/a3cc39fb1bc1498ce72d3257ea9a7e81 to your computer and use it in GitHub Desktop.
diff --git a/app/package.json b/app/package.json
index 7bf73e9..2c547a9 100644
--- a/app/package.json
+++ b/app/package.json
@@ -10,7 +10,8 @@
"react-redux": "^7.1.0",
"react-scripts": "3.0.1",
"react-transition-group": "^4.2.2",
- "redux": "^4.0.4"
+ "redux": "^4.0.4",
+ "rc-slider": "8.6.1"
},
"scripts": {
"start": "react-scripts start",
diff --git a/app/src/api.js b/app/src/api.js
index 1c1a80d..aa3e798 100644
--- a/app/src/api.js
+++ b/app/src/api.js
@@ -38,6 +38,12 @@ class APIClient {
});
}
+ setVolPct(vol_pct) {
+ return this.perform('post', '/vol_pct', {
+ vol_pct: vol_pct
+ });
+ }
+
clearQueue() {
return this.perform('post', '/clear');
}
diff --git a/app/src/component/app/app.js b/app/src/component/app/app.js
index e94f11a..239976f 100644
--- a/app/src/component/app/app.js
+++ b/app/src/component/app/app.js
@@ -13,6 +13,9 @@ import SearchResultVideo from 'dataobj/search_result_video';
import './app.css';
class App extends React.Component {
+
+ static QUEUE_POLL_INTERVAL_MS = 1000;
+
constructor(props) {
super(props);
@@ -35,7 +38,8 @@ class App extends React.Component {
playlist_videos: [],
color_mode: 'color',
last_queued_videos: [],
- last_queued_video_color_modes: []
+ last_queued_video_color_modes: [],
+ vol_pct: undefined
};
/* intro transition */
@@ -53,6 +57,7 @@ class App extends React.Component {
this.nextVideo = this.nextVideo.bind(this);
this.clearQueue = this.clearQueue.bind(this);
this.removeVideo = this.removeVideo.bind(this);
+ this.setVolPct = this.setVolPct.bind(this);
}
componentDidMount() {
@@ -99,6 +104,8 @@ class App extends React.Component {
nextVideo={this.nextVideo}
clearQueue={this.clearQueue}
removeVideo={this.removeVideo}
+ setVolPct={this.setVolPct}
+ vol_pct={this.state.vol_pct}
/>
</div>
</CSSTransition>
@@ -173,7 +180,7 @@ class App extends React.Component {
.finally(() => {
// need to do this on a timeout because the server isnt so great about
// the currently playing video immediately after skipping
- setTimeout(() => {this.getPlaylistQueue()}, 1000)
+ setTimeout(() => {this.getPlaylistQueue()}, App.QUEUE_POLL_INTERVAL_MS)
})
}
}
@@ -191,6 +198,9 @@ class App extends React.Component {
.removeVideo(video)
.finally(() => this.getPlaylistQueue())
}
+ setVolPct(vol_pct) {
+ return this.apiClient.setVolPct(vol_pct)
+ }
cancelQueuePoll() {
clearTimeout(this.queue_timeout);
@@ -198,7 +208,7 @@ class App extends React.Component {
getPlaylistQueue() {
if (this.state.playlist_loading) {
this.cancelQueuePoll();
- this.queue_timeout = setTimeout(this.getPlaylistQueue.bind(this), 1000);
+ this.queue_timeout = setTimeout(this.getPlaylistQueue.bind(this), App.QUEUE_POLL_INTERVAL_MS);
return;
}
@@ -209,6 +219,7 @@ class App extends React.Component {
.then((data) => {
if (data.success) {
var playlist_videos = PlaylistVideo.fromArray(data.queue);
+ var vol_pct = +(data.vol_pct.toFixed(0));
var playlist_current_video = this.state.playlist_current_video;
var current_video = playlist_videos.find(function(video) {
return video.status === 'STATUS_PLAYING';
@@ -234,14 +245,15 @@ class App extends React.Component {
this.setState({
playlist_current_video: playlist_current_video,
- playlist_videos: playlist_videos
+ playlist_videos: playlist_videos,
+ vol_pct: vol_pct
});
}
this.setState({ playlist_loading: false });
})
.finally((data) => {
- this.queue_timeout = setTimeout(this.getPlaylistQueue.bind(this), 1000);
+ this.queue_timeout = setTimeout(this.getPlaylistQueue.bind(this), App.QUEUE_POLL_INTERVAL_MS);
});
}
diff --git a/app/src/component/app/content.js b/app/src/component/app/content.js
index e619168..e1dd777 100644
--- a/app/src/component/app/content.js
+++ b/app/src/component/app/content.js
@@ -83,6 +83,8 @@ class Content extends React.Component {
nextVideo={this.props.nextVideo}
clearQueue={this.props.clearQueue}
removeVideo={this.props.removeVideo}
+ setVolPct={this.props.setVolPct}
+ vol_pct={this.props.vol_pct}
/>
</div>
</div>
@@ -108,6 +110,8 @@ class Content extends React.Component {
clearQueue={this.props.clearQueue}
removeVideo={this.props.removeVideo}
contractFooterPlaylist={this.contractFooterPlaylist}
+ setVolPct={this.props.setVolPct}
+ vol_pct={this.props.vol_pct}
/>
</div>
</div>
diff --git a/app/src/component/currently_playing/currently_playing.css b/app/src/component/currently_playing/currently_playing.css
index 35b943d..1f21b69 100644
--- a/app/src/component/currently_playing/currently_playing.css
+++ b/app/src/component/currently_playing/currently_playing.css
@@ -1,6 +1,10 @@
-.now_playing {
- min-height: 330px;
+input.volume {
+ width: 100%;
+}
+.vol-icon {
+ font-size:1.15rem;
+}
+.volume-container {
+ padding-left: 20px !important;
+ padding-right: 25px !important;
}
-.loading .video-thumbnail {
- opacity: .3;
-}
\ No newline at end of file
diff --git a/app/src/component/currently_playing/currently_playing_video.js b/app/src/component/currently_playing/currently_playing_video.js
index 0297b64..c67bcc2 100644
--- a/app/src/component/currently_playing/currently_playing_video.js
+++ b/app/src/component/currently_playing/currently_playing_video.js
@@ -1,15 +1,25 @@
+import './currently_playing.css';
+import App from '../app/app';
+
+import 'rc-slider/assets/index.css';
import React from 'react';
+import Slider from 'rc-slider';
class CurrentlyPlayingRight extends React.Component {
constructor(props) {
super(props);
this.handleSkip = this.handleSkip.bind(this);
+ this.state = {
+ vol_pct: this.props.vol_pct,
+ is_vol_locked: false,
+ is_vol_lock_releasable: true,
+ vol_lock_marked_releasable_time: 0
+ };
}
render() {
- var row_class = 'now-playing ' + (this.props.loading ? 'loading' : '')
-
+ var row_class = 'now-playing ' + (this.props.loading ? 'loading' : '');
return (
<div>
<div className={row_class}>
@@ -34,6 +44,42 @@ class CurrentlyPlayingRight extends React.Component {
</div>
</div>
+ <div className='row'>
+ <div className='col-1 p-0 text-right'><span className='glyphicon glyphicon-volume-down bg-light-text vol-icon' aria-hidden='true' /></div>
+ <div className='col-10 volume-container'>
+ <Slider
+ className='volume'
+ min={0}
+ max={100}
+ step={1}
+ onBeforeChange={this.grabVolMutex}
+ onChange={this.onVolChange}
+ onAfterChange={this.markVolMutexReleasable}
+ value={this.state.is_vol_locked ? this.state.vol_pct : this.props.vol_pct}
+ trackStyle={{
+ border: '1px solid #686E7B',
+ backgroundColor: '#686E7B',
+ height: 10
+ }}
+ railStyle={{
+ border: '1px solid #686E7B',
+ backgroundColor: 'transparent',
+ height: 10
+ }}
+ handleStyle={{
+ border: '1px solid #5cedf9',
+ boxShadow: '0px 0px 3.5px #52c6f3, 0px 0px 0px #6acef5',
+ height: 30,
+ width: 30,
+ marginLeft: -16,
+ marginTop: -9,
+ backgroundColor: '#2e3135',
+ }}
+ />
+ </div>
+ <div className='col-1 p-0'><span className='glyphicon glyphicon-volume-up bg-light-text vol-icon' aria-hidden='true' /></div>
+ </div>
+
<div className='container pt-2 px-0 mt-2'>
<div className='row mr-0'>
<div className='col-8 px-2 pl-3 small-vertical-center'>
@@ -61,6 +107,45 @@ class CurrentlyPlayingRight extends React.Component {
this.props.setLoading();
this.props.nextVideo();
}
+
+ onVolChange = (vol_pct) => {
+ this.props.setVolPct(vol_pct)
+ this.setState({vol_pct: vol_pct});
+ };
+
+ grabVolMutex = () => {
+ this.setState({
+ is_vol_locked: true,
+ is_vol_lock_releasable: false
+ });
+ };
+ markVolMutexReleasable = () => {
+ this.setState({
+ is_vol_lock_releasable: true,
+ vol_lock_marked_releasable_time: (new Date()).getTime()
+ });
+ };
+ releaseVolMutex = () => {
+ this.setState({
+ is_vol_locked: false,
+ is_vol_lock_releasable: true
+ });
+ };
+
+ // TODO: this is deprecated
+ componentWillReceiveProps(nextProps) {
+ if (this.state.is_vol_locked && this.state.is_vol_lock_releasable) {
+ var millis_since_vol_locked_marked_releasable = (new Date()).getTime() - this.state.vol_lock_marked_releasable_time;
+ if (millis_since_vol_locked_marked_releasable > (App.QUEUE_POLL_INTERVAL_MS + 500)) {
+ this.releaseVolMutex();
+ }
+ }
+
+ if (!this.state.is_vol_locked) {
+ this.setState({vol_pct: nextProps.vol_pct});
+ }
+ }
+
}
export default CurrentlyPlayingRight;
\ No newline at end of file
diff --git a/app/src/component/playlist/playlist.js b/app/src/component/playlist/playlist.js
index 3c9ee56..015b21b 100644
--- a/app/src/component/playlist/playlist.js
+++ b/app/src/component/playlist/playlist.js
@@ -51,6 +51,8 @@ class Playlist extends React.Component {
<LoadWithVideo video={this.props.current_video}>
<CurrentlyPlaying
nextVideo={this.props.nextVideo}
+ setVolPct={this.props.setVolPct}
+ vol_pct={this.props.vol_pct}
clearQueue={this.props.clearQueue}
/>
</LoadWithVideo>
diff --git a/app/src/component/playlist/playlist_expanded.js b/app/src/component/playlist/playlist_expanded.js
index fd03fa5..250bf6e 100644
--- a/app/src/component/playlist/playlist_expanded.js
+++ b/app/src/component/playlist/playlist_expanded.js
@@ -25,6 +25,8 @@ class PlaylistExpanded extends React.Component {
<LoadWithVideo video={this.props.current_video}>
<CurrentlyPlaying
nextVideo={this.props.nextVideo}
+ setVolPct={this.props.setVolPct}
+ vol_pct={this.props.vol_pct}
clearQueue={this.props.clearQueue}
/>
</LoadWithVideo>
diff --git a/lightness/queue.py b/lightness/queue.py
index c822f0c..eacc1fd 100644
--- a/lightness/queue.py
+++ b/lightness/queue.py
@@ -14,6 +14,7 @@ from lightness.videoplayer import VideoPlayer
from lightness.videoprocessor import VideoProcessor
from lightness.config import Config
from lightness.gameoflife import GameOfLife
+from lightness.volumecontroller import VolumeController
# The Queue is responsible for playing the next video in the Playlist
class Queue:
@@ -28,7 +29,10 @@ class Queue:
self.__config = Config()
self.__should_play_game_of_life = self.__config.get_queue_config('should_play_game_of_life', True)
self.__logger = Logger().set_namespace(self.__class__.__name__)
+
+ # house keeping
self.__clear_screen()
+ (VolumeController()).set_vol_pct(50)
self.__playlist.clean_up_state()
def run(self):
diff --git a/lightness/videoprocessor.py b/lightness/videoprocessor.py
index 1785ae3..e73f5e9 100644
--- a/lightness/videoprocessor.py
+++ b/lightness/videoprocessor.py
@@ -136,7 +136,6 @@ class VideoProcessor:
fps = self.__calculate_fps()
ffmpeg_to_python_fifo_name = self.__make_ffmpeg_to_python_fifo()
- self.__maybe_set_volume()
if self.__maybe_skip_video():
return
@@ -368,11 +367,6 @@ class VideoProcessor:
self.__logger.info('Calculated video fps: ' + str(fps))
return fps
- def __maybe_set_volume(self):
- if self.__video_settings.should_play_audio:
- self.__logger.info('Setting volume to 100%...')
- subprocess.check_output(('amixer', 'cset', 'numid=1', '100%'))
-
def __make_ffmpeg_to_python_fifo(self):
make_fifo_cmd = (
'fifo_name=$(mktemp --tmpdir={} --dry-run {}) && mkfifo -m 600 "$fifo_name" && printf $fifo_name'
diff --git a/lightness/volumecontroller.py b/lightness/volumecontroller.py
new file mode 100644
index 0000000..89a572e
--- /dev/null
+++ b/lightness/volumecontroller.py
@@ -0,0 +1,54 @@
+import subprocess
+import re
+import math
+
+class VolumeController:
+
+ """
+ sudo amixer cset numid=1 96.24%
+ numid=1,iface=MIXER,name='PCM Playback Volume'
+ ; type=INTEGER,access=rw---R--,values=1,min=-10239,max=400,step=0
+ : values=0
+ | dBscale-min=-102.39dB,step=0.01dB,mute=1
+
+ Setting 96.24% is equivalent to 0dB. Anything higher may result in clipping.
+ """
+ __LIMITED_MAX_VOL_VAL = 0
+
+ # amixer output: ; type=INTEGER,access=rw---R--,values=1,min=-10239,max=400,step=0
+ __GLOBAL_MIN_VOL_VAL = -10239
+ __GLOBAL_MAX_VOL_VAL = 400
+
+ # gets a perceptual loudness %
+ def get_vol_pct(self):
+ res = subprocess.check_output(('amixer', 'cget', 'numid=1')).decode("utf-8")
+ m = re.search(" values=(-?\d+)", res, re.MULTILINE)
+ if m == None:
+ return 0
+
+ vol_val = int(m.group(1))
+ if vol_val == self.__GLOBAL_MIN_VOL_VAL:
+ vol_pct = 0
+ else:
+ # convert from decibel attenuation amount to perceptual loudness %
+ # see: http://www.sengpielaudio.com/calculator-levelchange.htm
+ db_level = vol_val / 100
+ vol_pct = math.pow(2, (db_level / 10)) * 100
+ vol_pct = max(0, vol_pct)
+ vol_pct = min(100, vol_pct)
+ return vol_pct
+
+ # takes a perceptual loudness %
+ def set_vol_pct(self, vol_pct):
+ if (vol_pct <= 0):
+ db_level = self.__GLOBAL_MIN_VOL_VAL / 100
+ else:
+ # get the decibel adjustment required for the human perceived loudness %.
+ # see: http://www.sengpielaudio.com/calculator-levelchange.htm
+ db_level = 10 * math.log(vol_pct / 100, 2)
+
+ db_level = max(self.__GLOBAL_MIN_VOL_VAL/100, db_level)
+ db_level = min(self.__LIMITED_MAX_VOL_VAL, db_level)
+
+ pct_to_set = (((db_level * 100) - self.__GLOBAL_MIN_VOL_VAL) / (self.__GLOBAL_MAX_VOL_VAL - self.__GLOBAL_MIN_VOL_VAL)) * 100
+ subprocess.check_output(('amixer', 'cset', 'numid=1', '{}%'.format(pct_to_set)))
diff --git a/server b/server
index baebac1..b82215a 100755
--- a/server
+++ b/server
@@ -8,14 +8,17 @@ from lightness.playlist import Playlist
from lightness.logger import Logger
from lightness.config import Config
from lightness.directoryutils import DirectoryUtils
+from lightness.volumecontroller import VolumeController
class LightnessAPI():
__playlist = Playlist()
+ __vol_controller = VolumeController()
def get_queue(self):
response_details = {}
queue = self.__playlist.get_queue()
response_details['queue'] = queue
+ response_details['vol_pct'] = self.__vol_controller.get_vol_pct()
response_details['success'] = True
return response_details
@@ -49,6 +52,15 @@ class LightnessAPI():
response_details['success'] = True
return response_details
+ def set_vol_pct(self, post_data):
+ vol_pct = int(post_data['vol_pct'])
+ self.__vol_controller.set_vol_pct(vol_pct)
+ return {
+ 'vol_pct': vol_pct,
+ 'success': True
+ }
+
+
class LightnessServerRequestHandler(BaseHTTPRequestHandler):
__root_dir = None
@@ -121,6 +133,8 @@ class LightnessServerRequestHandler(BaseHTTPRequestHandler):
response = self.__api.remove(post_data)
elif path == 'clear':
response = self.__api.clear()
+ elif path == 'vol_pct':
+ response = self.__api.set_vol_pct(post_data)
else:
self.__do_404()
return
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment