[vlc-commits] python: example usage of media callbacks (#138)
albestro
git at videolan.org
Thu Jun 4 15:35:45 CEST 2020
vlc/python | branch: master | albestro <9337627+albestro at users.noreply.github.com> | Thu Jun 4 15:34:43 2020 +0200| [f79f4ef69860b1e997360a9e3b426e89402ed4bd] | committer: GitHub
python: example usage of media callbacks (#138)
Example app exhibiting usage of media callbacks.
Closes #88
> http://git.videolan.org/gitweb.cgi/vlc/python.git/?a=commit;h=f79f4ef69860b1e997360a9e3b426e89402ed4bd
---
examples/play_buffer.py | 207 ++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 207 insertions(+)
diff --git a/examples/play_buffer.py b/examples/play_buffer.py
new file mode 100755
index 0000000..99bc66c
--- /dev/null
+++ b/examples/play_buffer.py
@@ -0,0 +1,207 @@
+#!/usr/bin/env python3
+
+# Author: A.Invernizzi (@albestro on GitHub)
+# Date: Jun 03, 2020
+
+# MIT License <http://OpenSource.org/licenses/MIT>
+#
+# Copyright (C) 2020 -- A. Invernizzi, @albestro on github
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the "Software"),
+# to deal in the Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish, distribute, sublicense,
+# and/or sell copies of the Software, and to permit persons to whom the
+# Software is furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+# OTHER DEALINGS IN THE SOFTWARE.
+
+"""
+Example usage of VLC API function `libvlc_media_new_callbacks`
+This function allows to create a VLC media `libvlc_media_t` specifying custom
+callbacks where the user can define how to manage and read the stream of data.
+
+The general use case for this is when you have data in memory and you want to
+play it (e.g. audio stream from a web radio).
+
+In this example, we are going to read playable data from files in a specified
+folder. In case you would want to read from a file, it is not the best way to do it,
+but for the sake of this example we are going to read data into memory from files.
+
+The example tries to highlight the separation of concerns between the callbacks and
+the application logic, so it would hopefully make clear how to integrate the VLC API
+with existing libraries.
+
+In particular, we have two main parts:
+ - StreamProvider: which is a class that implements the logic; "scrape" a folder
+ for files with a specific extensions, and provide methods that retrieves data.
+ - VLC callabacks that uses a StreamProvider object
+"""
+
+import argparse
+import ctypes
+import os
+
+import vlc
+
+class StreamProviderDir(object):
+ def __init__(self, rootpath, file_ext):
+ self._media_files = []
+ self._rootpath = rootpath
+ self._file_ext = file_ext
+ self._index = 0
+
+ def open(self):
+ """
+ this function is responsible of opening the media.
+ it could have been done in the __init__, but it is just an example
+
+ in this case it scan the specified folder, but it could also scan a
+ remote url or whatever you prefer.
+ """
+
+ print("read file list")
+ for entry in os.listdir(self._rootpath):
+ if os.path.splitext(entry)[1] == f".{self._file_ext}":
+ self._media_files.append(os.path.join(self._rootpath, entry))
+ self._media_files.sort()
+
+ print("playlist:")
+ for index,media_file in enumerate(self._media_files):
+ print(f"[{index}] {media_file}")
+
+ def release_resources(self):
+ """
+ In this example this function is just a placeholder,
+ in a more complex example this may release resources after the usage,
+ e.g. closing the socket from where we retrieved media data
+ """
+ print("releasing stream provider")
+
+ def seek(self, offset):
+ """
+ Again, a placeholder, not useful for the example
+ """
+ print(f"requested seek with offset=", offset)
+
+ def get_data(self):
+ """
+ It reads the current file in the list and returns the binary data
+ In this example it reads from file, but it could have downloaded data from an url
+ """
+ print(f"reading file [{self._index}] ", end='')
+
+ if self._index == len(self._media_files):
+ print("file list is over")
+ return b''
+
+ print(f"{self._media_files[self._index]}")
+ with open(self._media_files[self._index], 'rb') as stream:
+ data = stream.read()
+
+ self._index = self._index + 1
+
+ return data
+
+
+# HERE THERE ARE THE CALLBACKS USED BY THE MEDIA CREATED IN THE "MAIN"
+# a callback in its simplest form is a python function decorated with the specific @vlc.CallbackDecorators.*
+
+ at vlc.CallbackDecorators.MediaOpenCb
+def media_open_cb(opaque, data_pointer, size_pointer):
+ print("OPEN", opaque, data_pointer, size_pointer)
+
+ stream_provider = ctypes.cast(opaque, ctypes.POINTER(ctypes.py_object)).contents.value
+
+ stream_provider.open()
+
+ data_pointer.contents.value = opaque
+ size_pointer.value = 1 ** 64 - 1
+
+ return 0
+
+ at vlc.CallbackDecorators.MediaReadCb
+def media_read_cb(opaque, buffer, length):
+ print("READ", opaque, buffer, length)
+
+ stream_provider = ctypes.cast(opaque, ctypes.POINTER(ctypes.py_object)).contents.value
+
+ new_data = stream_provider.get_data()
+ bytes_read = len(new_data)
+
+ if bytes_read > 0:
+ buffer_array = ctypes.cast(buffer, ctypes.POINTER(ctypes.c_char * bytes_read))
+ for index, b in enumerate(new_data):
+ buffer_array.contents[index] = ctypes.c_char(b)
+
+ print(f"just read f{bytes_read}B")
+ return bytes_read
+
+ at vlc.CallbackDecorators.MediaSeekCb
+def media_seek_cb(opaque, offset):
+ print("SEEK", opaque, offset)
+
+ stream_provider = ctypes.cast(opaque, ctypes.POINTER(ctypes.py_object)).contents.value
+
+ stream_proivder.seek(offset)
+
+ return 0
+
+ at vlc.CallbackDecorators.MediaCloseCb
+def media_close_cb(opaque):
+ print("CLOSE", opaque)
+
+ stream_provider = ctypes.cast(opaque, ctypes.POINTER(ctypes.py_object)).contents.value
+
+ stream_provider.release_resources()
+
+
+# MAIN
+if __name__ == '__main__':
+ parser = argparse.ArgumentParser(
+ description='play files found in specified media folder (in alphabetic order)',
+ formatter_class=argparse.ArgumentDefaultsHelpFormatter)
+ parser.add_argument(
+ 'media_folder',
+ help='where to find files to play')
+ parser.add_argument(
+ '--extension',
+ default='ts',
+ help='file extension of the files to play')
+ args = parser.parse_args()
+
+ # helper object acting as media data provider
+ # it is just to highlight how the opaque pointer in the callback can be used
+ # and that the logic can be isolated from the callbacks
+ stream_provider = StreamProviderDir(args.media_folder, args.extension)
+
+ # these two lines to highlight how to pass a python object using ctypes
+ # it is verbose, but you can see the steps required
+ stream_provider_obj = ctypes.py_object(stream_provider)
+ stream_provider_ptr = ctypes.byref(stream_provider_obj)
+
+ # create an instance of vlc
+ instance = vlc.Instance()
+
+ # setup the callbacks for the media
+ media = instance.media_new_callbacks(
+ media_open_cb,
+ media_read_cb,
+ media_seek_cb,
+ media_close_cb,
+ stream_provider_ptr)
+ player = media.player_new_from_media()
+
+ # play/stop
+ player.play()
+ input("press enter to quit")
+ player.stop()
More information about the vlc-commits
mailing list