mpv.py: Add play_context convenience function

This commit is contained in:
jaseg 2024-01-22 11:48:47 +01:00
parent c52a07e862
commit 141ec7d372
2 changed files with 57 additions and 0 deletions

37
mpv.py
View file

@ -1944,6 +1944,43 @@ class MPV(object):
return cb
return register
@contextmanager
def play_context(self):
""" Context manager for streaming bytes straight into libmpv.
This is a convenience wrapper around python_stream. play_context returns a write method, which you can use in
the body of the context manager to feed libmpv bytes. All bytes you feed in with write() in the body of a single
call of this context manager are treated as one single file. A queue is used internally, so this function is
thread-safe. The queue is unlimited, so it cannot block and is safe to call from async code. You can use this
function to stream chunked data, e.g. from the network.
Use it like this:
with m.play_context() as write:
with open(TESTVID, 'rb') as f:
while (chunk := f.read(65536)): # Get some chunks of bytes
write(chunk)
"""
q = queue.Queue()
frame = sys._getframe()
stream_name = f'__python_mpv_play_generator_{hash(frame)}'
EOF = frame # Get some unique object as EOF marker
@self.python_stream(stream_name)
def reader():
while (chunk := q.get()) is not EOF:
if chunk:
yield chunk
reader.unregister()
def write(chunk):
q.put(chunk)
# Start playback before yielding, the first call to reader() will block until write is called at least once.
self.play(f'python://{stream_name}')
yield write
q.put(EOF)
def python_stream_catchall(self, cb):
""" Register a catch-all python stream to be called when no name matches can be found. Use this decorator on a
function that takes a name argument and returns a (generator, size) tuple (with size being None if unknown).

View file

@ -643,6 +643,26 @@ class TestStreams(unittest.TestCase):
m.terminate()
disp.stop()
def test_play_context(self):
handler = mock.Mock()
disp = Display()
disp.start()
m = mpv.MPV(vo=testvo)
def cb(evt):
handler(evt.as_dict(decoder=mpv.lazy_decoder))
m.register_event_callback(cb)
with m.play_context() as write:
with open(TESTVID, 'rb') as f:
write(f.read(100))
write(f.read(1000))
write(f.read())
m.wait_for_playback()
handler.assert_any_call({'event': 'end-file', 'reason': 'eof', 'playlist_entry_id': 1})
m.terminate()
disp.stop()
class TestLifecycle(unittest.TestCase):