-
Notifications
You must be signed in to change notification settings - Fork 382
CustomPlugin
With just a few lines of code, you can publish your device as a DLNA renderer.
First, You need install macast via pip. see: https://github.com/xfangfang/Macast/wiki/Installation#pip
Then refer to the following example or MPVRenderer (located at macast_renderer/mpv.py) create your own CustomRenderer class which inherits the Renderer class.
# a minimal demo
# this demo will print the media url when you push a media file through your phone
from macast import cli, Renderer
class CustomRenderer(Renderer):
def set_media_url(self, url):
print(url)
if __name__ == '__main__':
cli(CustomRenderer())
Please imitate the following headers to write your headers, otherwise Macast will not recognize your Python file as a plugin.
# a minimal demo
# this demo will print the media url when you push a media file through your phone
# Macast Metadata
# <macast.title>DEMO Renderer</macast.title>
# <macast.renderer>CustomRenderer</macast.renderer>
# <macast.platform>darwin,win32,linux</macast.platform>
# <macast.version>0.1</macast.version>
# <macast.author>xfangfang</macast.author>
# <macast.desc>this demo will print the media url when you push a media file through your phone</macast.desc>
from macast import cli, Renderer
class CustomRenderer(Renderer):
def set_media_url(self, url):
print(url)
if __name__ == '__main__':
cli(CustomRenderer())
-
You need to implement part of the following method to get data from DLNA clients.(At least set_media_url needs to be implemented to obtain the media url from DLNA clients.)
Methods needed to be implemented:
def set_media_stop(self): pass def set_media_pause(self): pass def set_media_resume(self): pass def set_media_volume(self, data): """ data : int, range from 0 to 100 """ pass def set_media_mute(self, data): """ data : bool """ pass def set_media_url(self, data): """ data : string """ pass def set_media_title(self, data): """ data : string """ pass def set_media_position(self, data): """ data : string position, 00:00:00 """ pass
-
When the player(CustomRenderer) running on the computer is operated by the user, you need to call some of the following methods to notify the DLNA clients of the new changes.
Methods needed to be called:
# The current position of the media you are playing # ATTENTION: Not to call this method in the self.set_media_position callback # data : string, 00:00:00 self.set_state_position(data) # The length of the media you are playing # data : string, 00:00:00 self.set_state_duration(data): # Very important!!! # Call this function when the state of the played video changes # PLAYING and STOPPED must be implemented # data : string in [PLAYING, PAUSED_PLAYBACK, STOPPED] self.set_state_transport(data): # If there is an error in the media you play (for example, the file format is not supported), call this method self.set_state_transport_error(): # ATTENTION: Not to call this method in the self.set_media_mute callback # data : bool self.set_state_mute(data): # ATTENTION: Not to call this method in the self.set_media_volume callback # data : int, range from 0 to 100 self.set_state_volume(data)
The following examples do not contain Macast metadata
. Please go to Macast-plugins to find the complete code.
Many people asked me if Macast could support IINA. Now a few lines of code which add supporting of iina for Macast is here.
This example only supports the control of IINA‘s start and stop. You can complete more functions base the code below.
In the future, macast will support the selection of players. Welcome to contribute your code then.
IINA Renderer code:
# Copyright (c) 2021 by xfangfang. All Rights Reserved.
#
# Using iina as DLNA media renderer
#
import os
import time
import threading
import subprocess
from macast import cli
from macast.renderer import Renderer
IINA_PATH = '/Applications/IINA.app/Contents/MacOS/iina-cli'
class IINARenderer(Renderer):
def __init__(self):
super(IINARenderer, self).__init__()
self.start_position = 0
self.position_thread_running = True
self.position_thread = threading.Thread(target=self.position_tick, daemon=True)
self.position_thread.start()
# a thread is started here to increase the playback position once per second
# to simulate that the media is playing.
self.iina = None
def position_tick(self):
while self.position_thread_running:
time.sleep(1)
self.start_position += 1
sec = self.start_position
position = '%d:%02d:%02d' % (sec // 3600, (sec % 3600) // 60, sec % 60)
self.set_state_position(position)
def set_media_stop(self):
if self.iina is not None:
self.iina.terminate()
try:
os.waitpid(-1, 1)
except Exception as e:
pass
self.set_state_transport('STOPPED')
def set_media_url(self, url):
self.start_position = 0
self.iina = subprocess.Popen([IINA_PATH, '--keep-running', url])
self.set_state_transport("PLAYING")
def stop(self):
super(IINARenderer, self).stop()
self.set_media_stop()
if __name__ == '__main__':
cli(IINARenderer())
The code is almost the same as that of IINA, only slightly different when the player is turned on and off.
PotPlayer Renderer code:
# Copyright (c) 2021 by xfangfang. All Rights Reserved.
#
# Using potplayer as DLNA media renderer
#
import os
import time
import threading
import subprocess
from macast import cli, gui
from macast.renderer import Renderer
# POTPLAYER_PATH = '%HOMEPATH%\\Downloads\\PotPlayer64\\PotPlayermini64.exe'
POTPLAYER_PATH = '"C:\\Program Files\\PotPlayer64\\PotPlayermini64.exe"'
class PotplayerRenderer(Renderer):
def __init__(self):
super(PotplayerRenderer, self).__init__()
self.start_position = 0
self.position_thread_running = True
self.position_thread = threading.Thread(target=self.position_tick, daemon=True)
self.position_thread.start()
# a thread is started here to increase the playback position once per second
# to simulate that the media is playing.
def position_tick(self):
while self.position_thread_running:
time.sleep(1)
self.start_position += 1
sec = self.start_position
position = '%d:%02d:%02d' % (sec // 3600, (sec % 3600) // 60, sec % 60)
self.set_state_position(position)
def set_media_stop(self):
subprocess.Popen(['taskkill', '/f', '/im', 'PotPlayerMini64.exe']).communicate()
self.set_state_transport('STOPPED')
def set_media_url(self, url):
self.set_media_stop()
self.start_position = 0
subprocess.Popen('{} "{}"'.format(POTPLAYER_PATH, url), shell=True)
self.set_state_transport("PLAYING")
def stop(self):
super(PotplayerRenderer, self).stop()
self.set_media_stop()
print("PotPlayer stop")
def start(self):
super(PotplayerRenderer, self).start()
print("PotPlayer start")
if __name__ == '__main__':
gui(PotplayerRenderer())
# or using cli to disable taskbar menu
# cli(PotplayerRenderer())
This example runs on raspberry pi,it receives MP3 audio from DLNA client, and sends FM broadcast through PiFmRds
Warning and Disclaimer
PiFmRds is an experimental program, designed only for experimentation. It is in no way intended to become a personal media center or a tool to operate a radio station, or even broadcast sound to one's own stereo system.In most countries, transmitting radio waves without a state-issued licence specific to the transmission modalities (frequency, power, bandwidth, etc.) is illegal.
Therefore, always connect a shielded transmission line from the RaspberryPi directly to a radio receiver, so as not to emit radio waves. Never use an antenna.
Even if you are a licensed amateur radio operator, using PiFmRds to transmit radio waves on ham frequencies without any filtering between the RaspberryPi and an antenna is most probably illegal because the square-wave carrier is very rich in harmonics, so the bandwidth requirements are likely not met.
I could not be held liable for any misuse of your own Raspberry Pi. Any experiment is made under your own responsibility.
PI FM RDS Renderer code:
# Copyright (c) 2021 by xfangfang. All Rights Reserved.
#
# Using pi_fm_rds as DLNA media renderer
# https://github.com/ChristopheJacquet/PiFmRds
#
import os
import time
import threading
import subprocess
from macast import cli, gui
from macast.renderer import Renderer
class PIFMRenderer(Renderer):
def __init__(self):
super(PIFMRenderer, self).__init__()
self.start_position = 0
self.position_thread_running = True
self.position_thread = threading.Thread(target=self.position_tick, daemon=True)
self.position_thread.start()
# Because the playback progress cannot be easily obtained from **sox**,
# a thread is started here to increase the playback position once per second
# to simulate that the audio is playing.
self.sox = None
self.fm = subprocess.Popen(['sudo', 'pi_fm_rds', '-freq', '108', '-audio', '-'],
stdin=subprocess.PIPE,
bufsize=1024)
def position_tick(self):
while self.position_thread_running:
time.sleep(1)
self.start_position += 1
sec = self.start_position
position = '%d:%02d:%02d' % (sec // 3600, (sec % 3600) // 60, sec % 60)
self.set_state_position(position)
def set_media_stop(self):
if self.sox is not None:
self.sox.terminate()
os.waitpid(-1, 1)
self.set_state_transport('STOPPED')
def set_media_url(self, data):
self.start_position = 0
self.sox = subprocess.Popen(['sox', '-t', 'mp3', data, '-t', 'wav', '-'],
stdout=self.fm.stdin)
self.set_state_transport("PLAYING")
def stop(self):
super(PIFMRenderer, self).stop()
os._exit(0)
if __name__ == '__main__':
cli(PIFMRenderer())
# or using gui
# gui(PIFMRenderer())