Created
September 30, 2019 03:20
-
-
Save dasl-/e789788d2b5e9eac881ba3a9d0be9c79 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/app/package.json b/app/package.json | |
index 7bf73e9..10a4132 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.7.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..ad2615b 100644 | |
--- a/app/src/component/app/app.js | |
+++ b/app/src/component/app/app.js | |
@@ -35,7 +35,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 +54,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 +101,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> | |
@@ -191,6 +195,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); | |
@@ -209,6 +216,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,7 +242,8 @@ 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 | |
}); | |
} | |
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_footer.js b/app/src/component/currently_playing/currently_playing_footer.js | |
index 8ec1c6c..d79198e 100644 | |
--- a/app/src/component/currently_playing/currently_playing_footer.js | |
+++ b/app/src/component/currently_playing/currently_playing_footer.js | |
@@ -1,4 +1,5 @@ | |
import React from 'react'; | |
+import './currently_playing.css'; | |
class CurrentlyPlayingFooter extends React.Component { | |
constructor(props) { | |
diff --git a/app/src/component/currently_playing/currently_playing_video.js b/app/src/component/currently_playing/currently_playing_video.js | |
index 0297b64..38caa0d 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 '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 | |
+ }; | |
+ console.log(this.props.vol_pct); | |
} | |
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: 0, | |
+ 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,40 @@ class CurrentlyPlayingRight extends React.Component { | |
this.props.setLoading(); | |
this.props.nextVideo(); | |
} | |
+ | |
+ onVolChange = (vol_pct) => { | |
+ console.log("changed: " + 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 | |
+ }); | |
+ }; | |
+ | |
+ componentWillReceiveProps(nextProps) { | |
+ /* TODO: use server side timestamps for this */ | |
+ var millis_since_vol_locked_marked_releasable = (new Date()).getTime() - this.state.vol_lock_marked_releasable_time; | |
+ if (this.state.is_vol_locked && this.state.is_vol_lock_releasable && millis_since_vol_locked_marked_releasable > 1500) { | |
+ this.releaseVolMutex(); | |
+ } | |
+ } | |
+ | |
} | |
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/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