-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #4 from unparalleled-js/feat/custom-playe
feat: support for AFPlay
- Loading branch information
Showing
19 changed files
with
378 additions
and
83 deletions.
There are no files selected for viewing
This file contains 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
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import click | ||
|
||
from audius.types import PlayerType | ||
|
||
|
||
def player_option(): | ||
return click.option( | ||
"--player", | ||
help="The player to use.", | ||
type=click.Choice([x.value for x in PlayerType.__members__.values()], case_sensitive=False), | ||
callback=lambda _, _2, val: PlayerType(val), | ||
) |
This file contains 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
This file contains 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
This file contains 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
This file was deleted.
Oops, something went wrong.
This file contains 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
This file was deleted.
Oops, something went wrong.
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
from typing import TYPE_CHECKING, Dict, Optional, Type | ||
|
||
from audius.client import API | ||
from audius.exceptions import MissingPlayerError | ||
from audius.player.af import AFPlayer | ||
from audius.player.base import BasePlayer | ||
from audius.player.vlc import VLCPlayer | ||
from audius.types import PlayerType | ||
|
||
if TYPE_CHECKING: | ||
from audius.sdk import Audius | ||
|
||
|
||
class Player(API): | ||
def __init__(self, sdk: "Audius") -> None: | ||
super().__init__(sdk) | ||
self._player_classes: Dict[PlayerType, Type] = { | ||
PlayerType.AFPLAY: AFPlayer, | ||
PlayerType.VLC: VLCPlayer, | ||
} | ||
self._player_map: Dict[PlayerType, BasePlayer] = {} | ||
|
||
def play(self, url: str, player_type: Optional[PlayerType] = None): | ||
player = self.get_player(player_type=player_type) | ||
player.play(url) | ||
|
||
def display_now_playing(self, track: Dict, player_type: Optional[PlayerType] = None): | ||
player = self.get_player(player_type=player_type) | ||
player.display_now_playing(track) | ||
|
||
def get_player(self, player_type: Optional[PlayerType] = None) -> BasePlayer: | ||
player_type = player_type or self.config.player | ||
if player_type is not None: | ||
if player_type not in self._player_classes: | ||
raise ValueError(f"Unknown player type '{player_type}'") | ||
|
||
self._player_map[player_type] = self._player_classes[player_type](self.sdk) | ||
return self._player_map[player_type] | ||
|
||
if self._player_map: | ||
# Use previously connected player. | ||
player_type = next(iter(self._player_map)) | ||
return self._player_map[player_type] | ||
|
||
# Find an available player. | ||
for player_cls in self._player_classes.values(): | ||
player = player_cls(self.sdk) | ||
if player.is_available(): | ||
self._player_map[player._type] = player | ||
return player | ||
|
||
raise MissingPlayerError() |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import os | ||
import subprocess | ||
import tempfile | ||
import threading | ||
import time | ||
from typing import TYPE_CHECKING | ||
|
||
from audius.player.base import BasePlayer | ||
from audius.types import PlayerType | ||
|
||
if TYPE_CHECKING: | ||
from audius.sdk import Audius | ||
|
||
|
||
class AFPlayer(BasePlayer): | ||
def __init__(self, sdk: "Audius"): | ||
super().__init__(PlayerType.AFPLAY, sdk) | ||
|
||
def is_available(self): | ||
try: | ||
subprocess.run("afplay", stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) | ||
return True | ||
except FileNotFoundError: | ||
return False | ||
|
||
def play(self, url: str): | ||
download_url = self.client.get_redirect_url(url) | ||
with tempfile.NamedTemporaryFile(mode="w+b", delete=False) as _file: | ||
fd3 = os.dup(_file.fileno()) | ||
|
||
def download(): | ||
self.sdk.tracks.download(download_url, fd3, hide_output=True) | ||
|
||
# Stream the song while playing it to prevent waiting | ||
# for entire track to finish download. | ||
thread = threading.Thread(target=download) | ||
thread.start() | ||
time.sleep(5) # Buffer | ||
subprocess.Popen(["afplay", _file.name]) | ||
thread.join() | ||
time.sleep(1) |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
from abc import abstractmethod | ||
from typing import TYPE_CHECKING, Dict | ||
|
||
import click | ||
|
||
from audius.client import API | ||
from audius.types import PlayerType | ||
|
||
if TYPE_CHECKING: | ||
from audius.sdk import Audius | ||
|
||
|
||
class BasePlayer(API): | ||
def __init__(self, player_type: PlayerType, sdk: "Audius"): | ||
self._type = player_type | ||
super().__init__(sdk) | ||
|
||
@abstractmethod | ||
def is_available(self) -> bool: | ||
""" | ||
Returns True if this play is working. | ||
""" | ||
|
||
@abstractmethod | ||
def play(self, url: str): | ||
""" | ||
Stream and play track from Audius. | ||
Player-subclasses must implement this method. | ||
""" | ||
|
||
def display_now_playing(self, track: Dict): | ||
click.echo( | ||
f"({self._type.value.lower().capitalize()}) " | ||
f"Now playing '{track['title']}' by {track['user']['name']}" | ||
) |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
import time | ||
from functools import cached_property | ||
from typing import TYPE_CHECKING | ||
|
||
from audius.exceptions import MissingPlayerError | ||
from audius.player.base import BasePlayer | ||
from audius.types import PlayerType | ||
|
||
if TYPE_CHECKING: | ||
from audius.sdk import Audius | ||
|
||
|
||
class VLCPlayer(BasePlayer): | ||
def __init__(self, sdk: "Audius"): | ||
super().__init__(PlayerType.VLC, sdk) | ||
|
||
@cached_property | ||
def vlc(self): | ||
# Lazy load to allow SDK to work when VLC not installed. | ||
try: | ||
import vlc # type: ignore | ||
|
||
except Exception: | ||
raise MissingPlayerError() | ||
|
||
return vlc | ||
|
||
@cached_property | ||
def _player(self): | ||
return self.vlc.MediaPlayer() | ||
|
||
def is_available(self) -> bool: | ||
try: | ||
_ = self.vlc | ||
return True | ||
except MissingPlayerError: | ||
return False | ||
|
||
def play(self, url: str): | ||
media = self.vlc.Media(url) | ||
self._player.set_media(media) | ||
self._player.play() | ||
time.sleep(5) # Wait 5 seconds for it to start. | ||
while self._player.is_playing(): | ||
time.sleep(1) |
Oops, something went wrong.