diff options
author | jaseg <git@jaseg.de> | 2024-01-22 11:48:47 +0100 |
---|---|---|
committer | jaseg <git@jaseg.de> | 2024-01-22 11:53:02 +0100 |
commit | 141ec7d372c8d536c208c39bb89132d69aa1b36c (patch) | |
tree | 219adac86c26d51a08a60984e8696e050e77131c | |
parent | c52a07e86279f8a892aee4b2adae2a03481f7bce (diff) | |
download | python-mpv-141ec7d372c8d536c208c39bb89132d69aa1b36c.tar.gz python-mpv-141ec7d372c8d536c208c39bb89132d69aa1b36c.tar.bz2 python-mpv-141ec7d372c8d536c208c39bb89132d69aa1b36c.zip |
mpv.py: Add play_context convenience function
-rw-r--r-- | mpv.py | 37 | ||||
-rwxr-xr-x | tests/test_mpv.py | 20 |
2 files changed, 57 insertions, 0 deletions
@@ -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). diff --git a/tests/test_mpv.py b/tests/test_mpv.py index 74999ad..9cc688d 100755 --- a/tests/test_mpv.py +++ b/tests/test_mpv.py @@ -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): |