Skip to content

Commit

Permalink
player: simple implementation of fade in-out (#763)
Browse files Browse the repository at this point in the history
  • Loading branch information
mokurin000 authored Jan 16, 2024
1 parent 901f9a3 commit bbc8da9
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 6 deletions.
5 changes: 4 additions & 1 deletion feeluown/app/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,10 @@ def __init__(self, args, config, **kwargs):
# TODO: initialization should be moved into initialize
Resolver.library = self.library
# Player.
self.player = Player(audio_device=bytes(config.MPV_AUDIO_DEVICE, 'utf-8'))
self.player = Player(
audio_device=bytes(config.MPV_AUDIO_DEVICE, 'utf-8'),
fade=config.PLAYBACK_CROSSFADE
)
# Theoretically, each caller maintain its own position delegate.
# For simplicity, this delegate is created for common use cases and
#
Expand Down
1 change: 1 addition & 0 deletions feeluown/app/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,5 @@ def create_config() -> Config:
config.deffield('NOTIFY_ON_TRACK_CHANGED', type_=bool, default=False,
desc='切换歌曲时显示桌面通知')
config.deffield('NOTIFY_DURATION', type_=int, default=3000, desc='桌面通知保留时长(ms)')
config.deffield('PLAYBACK_CROSSFADE', type_=bool, default=False, desc='播放暂停淡入淡出')
return config
79 changes: 74 additions & 5 deletions feeluown/player/mpvplayer.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import locale
import logging
import time
import math
import os

from mpv import ( # type: ignore
MPV,
Expand All @@ -11,6 +14,7 @@
ErrorCode,
)

from threading import Thread, RLock
from feeluown.utils.dispatch import Signal
from feeluown.media import Media, VideoAudioManifest
from .base_player import AbstractPlayer, State
Expand All @@ -28,7 +32,7 @@ class MpvPlayer(AbstractPlayer):
todo: make me singleton
"""
def __init__(self, _=None, audio_device=b'auto', winid=None, **kwargs):
def __init__(self, _=None, audio_device=b'auto', winid=None, fade=False, **kwargs):
"""
:param _: keep this arg to keep backward compatibility
"""
Expand Down Expand Up @@ -80,6 +84,10 @@ def __init__(self, _=None, audio_device=b'auto', winid=None, **kwargs):
self._mpv._event_callbacks.append(self._on_event)
logger.debug('Player initialize finished.')

self.do_fade = fade
if self.do_fade:
self.fade_lock = RLock()

def shutdown(self):
# The mpv has already been terminated.
# The mpv can't terminate twice.
Expand Down Expand Up @@ -152,20 +160,81 @@ def set_play_range(self, start=None, end=None):
self.seeked.emit(start)
_mpv_set_option_string(self._mpv.handle, b'end', bytes(end_str, 'utf-8'))

def fade(self, fade_in: bool, max_volume=None, callback=None):
self.fade_lock.acquire()

# k: factor between 0 and 1, to represent tick/fade_time
def fade_curve(k: float, fade_in: bool) -> float:
if fade_in:
return (1-math.cos(k*math.pi)) / 2
else:
return (1+math.cos(k*math.pi)) / 2

def set_volume(max_volume: int, fade_in: bool):
# https://bugs.python.org/issue31539#msg302699
if os.name == "nt":
freq = 25
interval = 0.02
else:
freq = 50
interval = 0.01

for _tick in range(freq):
new_volume = math.ceil(
fade_curve(_tick/freq, fade_in=fade_in)*max_volume
)
self.volume = new_volume
time.sleep(interval)

if max_volume:
volume = max_volume
else:
volume = self.volume

set_volume(volume, fade_in=fade_in)

if callback is not None:
callback()

self.volume = volume
self.fade_lock.release()

def resume(self):
if self.do_fade:
_volume = self.volume
self.volume = 0

self._mpv.pause = False
self.state = State.playing

def pause(self):
if self.do_fade:
fade_thread = Thread(
target=self.fade,
kwargs={"fade_in": True,
"max_volume": _volume}
)
fade_thread.start()

def _pause(self):
self._mpv.pause = True
self.state = State.paused

def pause(self):
if self.do_fade:
fade_thread = Thread(
target=self.fade,
kwargs={"fade_in": False,
"callback": self._pause}
)
fade_thread.start()
else:
self._pause()

def toggle(self):
self._mpv.pause = not self._mpv.pause
if self._mpv.pause:
self.state = State.paused
self.resume()
else:
self.state = State.playing
self.pause()

def stop(self):
self._mpv.pause = True
Expand Down

0 comments on commit bbc8da9

Please sign in to comment.