Skip to content

Commit

Permalink
Merge branch 'feeluown:master' into dev-4.0a0
Browse files Browse the repository at this point in the history
  • Loading branch information
mokurin000 authored Jan 16, 2024
2 parents 6dafac9 + e7994ff commit a374585
Show file tree
Hide file tree
Showing 8 changed files with 98 additions and 16 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
17 changes: 12 additions & 5 deletions feeluown/gui/components/menu.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
from typing import Optional

from feeluown.app import App
from feeluown.excs import ProviderIOError
from feeluown.utils.aio import run_fn, run_afn
from feeluown.player import SongRadio
from feeluown.library import SongProtocol, VideoModel
from feeluown.library import SongProtocol, VideoModel, SupportsSongMV

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -81,7 +82,11 @@ def _hover_mv(self, action, data):

def mv_fetched_cb(future):
self._fetching_mv = False
mv: Optional[VideoModel] = future.result()
try:
mv: Optional[VideoModel] = future.result()
except ProviderIOError as e:
logger.error(f"fetch song mv failed {e}")
mv = None
if mv is not None:
try:
mv_action = action.menu().addAction(mv.title)
Expand All @@ -103,9 +108,11 @@ def mv_fetched_cb(future):
if data['mvs'] is None and self._fetching_mv is False:
logger.debug('fetch song.mv for actions')
song = data['song']
self._fetching_mv = True
task = run_fn(self._app.library.song_get_mv, song)
task.add_done_callback(mv_fetched_cb)
provider = self._app.library.get(song.source)
if provider is not None and isinstance(provider, SupportsSongMV):
self._fetching_mv = True
task = run_fn(provider.song_get_mv, song)
task.add_done_callback(mv_fetched_cb)

def _hover_artists(self, action, data):
# pylint: disable=unnecessary-direct-lambda-call
Expand Down
4 changes: 3 additions & 1 deletion feeluown/gui/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ def get_from_cache(self, img_name):
return None

async def get(self, img_url, img_name):
if not img_url:
return None
if img_url.startswith('fuo://local'):
# Before, `models.uri.resolve` is uesd to handle these non-std paths,
# and it is not elegant in fact :(
Expand All @@ -45,7 +47,7 @@ async def get(self, img_url, img_name):
return provider.handle_with_path(img_url[11:])
fpath = self.cache.get(img_name)
if fpath is not None:
logger.info('read image:%s from cache', img_name)
logger.debug('read image:%s from cache', img_name)
with open(fpath, 'rb') as f:
content = f.read()
self.cache.update(img_name)
Expand Down
1 change: 0 additions & 1 deletion feeluown/gui/watch.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,6 @@ def set_mode(self, mode):

def on_media_changed(self, media):
if not media:
logger.info('No media is played, set mode to none')
self.set_mode(Mode.none)

def on_video_format_changed(self, video_format):
Expand Down
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
4 changes: 2 additions & 2 deletions feeluown/utils/request.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ def __init__(self):
self.server_error_signal = Signal()

def get(self, *args, **kw):
logger.info('request.get %s %s' % (args, kw))
logger.debug('request.get %s %s' % (args, kw))
if kw.get('timeout') is None:
kw['timeout'] = 1
try:
Expand All @@ -32,7 +32,7 @@ def get(self, *args, **kw):
return None

def post(self, *args, **kw):
logger.info('request.post %s %s' % (args, kw))
logger.debug('request.post %s %s' % (args, kw))
try:
res = requests.post(*args, **kw)
return res
Expand Down
3 changes: 2 additions & 1 deletion feeluown/webserver/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@

logger = logging.getLogger(__name__)

sanic_app = Sanic('FeelUOwn')
# Disable sanic's logging so that it can use feeluown's logging system.
sanic_app = Sanic('FeelUOwn', configure_logging=False)


def resp(js):
Expand Down

0 comments on commit a374585

Please sign in to comment.