Skip to content

Instantly share code, notes, and snippets.

@ivlevdenis
Last active August 16, 2022 16:05
Show Gist options
  • Save ivlevdenis/02d1017ccf860276ab7d to your computer and use it in GitHub Desktop.
Save ivlevdenis/02d1017ccf860276ab7d to your computer and use it in GitHub Desktop.
kivy android native videoview
import os
import re
from kivy.logger import Logger
from kivy.properties import (ObjectProperty, BooleanProperty, StringProperty,
OptionProperty, NumericProperty, ReferenceListProperty)
from kivy.uix.widget import Widget
from kivy.utils import platform
from jnius import PythonJavaClass, autoclass, java_method, cast
if platform == 'android':
from android.runnable import run_on_ui_thread
VideoView = autoclass('android.widget.VideoView')
URI = autoclass('android.net.Uri')
Gravity = autoclass('android.view.Gravity')
LinearLayout = autoclass('android.widget.LinearLayout')
LayoutParams = autoclass('android.widget.LinearLayout$LayoutParams')
activity = autoclass('org.renpy.android.PythonActivity').mActivity
class VideoPreparedCallback(PythonJavaClass):
# http://developer.android.com/reference/android/media/MediaPlayer.OnPreparedListener.html
__javainterfaces__ = ('android.media.MediaPlayer$OnPreparedListener', )
def __init__(self, callback):
super(VideoPreparedCallback, self).__init__()
self.callback = callback
@java_method('(Landroid/media/MediaPlayer;)V')
def onPrepared(self, mp):
self.callback(mp)
# class VideoInfoCallback(PythonJavaClass):
# http://developer.android.com/reference/android/media/MediaPlayer.OnInfoListener.html
# __javainterfaces__ = ('android.media.MediaPlayer$OnInfoListener', )
# def __init__(self, callback):
# super(VideoInfoCallback, self).__init__()
# self.callback = callback
# @java_method('(Landroid/media/MediaPlayer;II)B')
# def onInfo(self, mp, what, extra):
# """True if the method handled the info, false if it didn't.
# Returning false, or not having an OnErrorListener at all,
# will cause the info to be discarded."""
# self.callback(mp, what, extra)
class VideoErrorCallback(PythonJavaClass):
# http://developer.android.com/reference/android/media/MediaPlayer.OnErrorListener.html
__javainterfaces__ = ('android.media.MediaPlayer$OnErrorListener', )
def __init__(self, callback):
super(VideoErrorCallback, self).__init__()
self.callback = callback
@java_method('(Landroid/media/MediaPlayer;II)B')
def onError(self, mp, what, extra):
"""True if the method handled the error, false if it didn't.
Returning false, or not having an OnErrorListener at all,
will cause the OnCompletionListener to be called."""
self.callback(mp, what, extra)
class VideoCompletionCallback(PythonJavaClass):
# http://developer.android.com/reference/android/media/MediaPlayer.OnCompletionListener.html
__javainterfaces__ = ('android.media.MediaPlayer$OnCompletionListener', )
def __init__(self, callback):
super(VideoCompletionCallback, self).__init__()
self.callback = callback
@java_method('(Landroid/media/MediaPlayer;)V')
def onCompletion(self, mp):
self.callback(mp)
class AndroidWidgetHolder(Widget):
'''Act as a placeholder for an Android widget.
It will automatically add / remove the android view depending if the widget
view is set or not. The android view will act as an overlay,
so any graphics instruction in this area will be covered by the overlay.
'''
view = ObjectProperty(allownone=True)
'''Must be an Android View
'''
def __init__(self, **kwargs):
self._old_view = None
from kivy.core.window import Window
self._window = Window
kwargs['size_hint'] = (None, None)
super(AndroidWidgetHolder, self).__init__(**kwargs)
def on_view(self, instance, view):
self._on_view(instance, view)
@run_on_ui_thread
def _on_view(self, instance, view):
if self._old_view is not None:
layout = cast(LinearLayout, self._old_view.getParent())
layout.removeView(self._old_view)
self._old_view = None
if view is None:
return
# activity = PythonActivity.mActivity
lp = LayoutParams(*self.size)
lp.gravity = Gravity.CENTER
activity.addContentView(view, lp)
view.setZOrderOnTop(True)
view.setX(self.x)
view.setY(self._window.height - self.y - self.height)
self._old_view = view
def on_size(self, instance, size):
self._on_size(instance, size)
@run_on_ui_thread
def _on_size(self, instance, size):
if self.view:
Logger.info('AndroidWidgetHolder: Set size to {0}'.format(size))
params = self.view.getLayoutParams()
params.width = self.width
params.height = self.height
params.gravity = Gravity.CENTER
lp = LayoutParams(*self.size)
lp.gravity = Gravity.CENTER
self.view.setLayoutParams(lp)
# self.view.setX(self.x + self.width)
self.view.setY(self._window.height - self.y - self.height)
v = self.view
self.view = None
self.view = v
def on_x(self, instance, x):
self._on_x(instance, x)
@run_on_ui_thread
def _on_x(self, instance, x):
if self.view:
self.view.setX(x)
def on_y(self, instance, y):
self._on_y(instance, y)
@run_on_ui_thread
def _on_y(self, instance, y):
if self.view:
self.view.setY(self._window.height - self.y - self.height)
class AndroidVideoView(Widget):
norm_image_width = NumericProperty(0)
norm_image_height = NumericProperty(0)
norm_image_size = ReferenceListProperty(
norm_image_width, norm_image_height)
autoplay = BooleanProperty(False)
duration = NumericProperty(-1)
eos = BooleanProperty(False)
filename = StringProperty('')
loaded = BooleanProperty(False)
options = ObjectProperty({})
position = NumericProperty(-1)
state = OptionProperty('stop', options=['play', 'pause', 'stop'])
def __init__(self, **kwargs):
self._holder = None
self.videoview = None
super(AndroidVideoView, self).__init__(**kwargs)
self._holder = AndroidWidgetHolder(size=self.size, pos=self.pos)
self.add_widget(self._holder)
self.create_videoview()
@run_on_ui_thread
def create_videoview(self, *args):
self.videoview = VideoView(activity)
Logger.info('AndroidVideoView: Create new VideoView widget')
self._holder.view = self.videoview
self._prepared_callback = VideoPreparedCallback(self._on_prepared)
self.videoview.setOnPreparedListener(self._prepared_callback)
# self._info_callback = VideoInfoCallback(self._on_info)
# self.videoview.setOnInfoListener(self._info_callback)
self._error_callback = VideoErrorCallback(self._on_error)
self.videoview.setOnErrorListener(self._error_callback)
self._complete_callback = VideoCompletionCallback(self._on_completion)
self.videoview.setOnCompletionListener(self._complete_callback)
def _on_prepared(self, mp):
self.norm_image_width = mp.getVideoWidth()
self.norm_image_height = mp.getVideoHeight()
self.loaded = True
# def _on_info(self, mp, what, extra):
# pass
def _on_error(self, mp, what, extra):
self.loaded = False
self.norm_image_width = 0
self.norm_image_height = 0
def _on_completion(self, mp):
self.state = 'stop'
self.eos = True
def on_autoplay(self, instance, value):
if self.videoview is not None and self.loaded and value:
self.videoview.start()
Logger.info('AndroidVideoView: Set autoplay to {0}'.format(value))
def on_filename(self, instance, value):
self._on_filename(instance, value)
@run_on_ui_thread
def _on_filename(self, instance, value):
if re.match('(?:http|ftp|https|file)://', value) is not None:
fn = URI.parse(value)
self.videoview.setVideoURI(fn)
elif os.path.isfile(value):
self.videoview.setVideoPath(value)
else:
Logger.error('AndroidVideoView: File not found or \
not supported URI schema "{0}"'.format(value))
return
Logger.info('AndroidVideoView: Set filename to {0}'.format(value))
def on_loaded(self, instance, value):
self._on_loaded(instance, value)
@run_on_ui_thread
def _on_loaded(self, instance, value):
if value:
self.pos = (self.x + (self.width - self.norm_image_width)/2, self.pos[1])
self.duration = self.videoview.getDuration()
if self.autoplay:
self.state = 'play'
if self.state == 'play' and not self.videoview.isPlaying():
self.videoview.start()
Logger.info('AndroidVideoView: Set loaded to {0}'.format(value))
# def _on_position(self, dt):
# if self.videoview.isPlaying():
# self.position = self.videoview.getDuration()
# def on_position(self, instance, value):
# if value < 0:
# return
# Clock.schedule_once(self._on_position, 0.5)
def on_state(self, instance, value):
self._on_state(instance, value)
@run_on_ui_thread
def _on_state(self, instance, value):
if value == 'play':
if self.loaded:
self.videoview.start()
self.eos = False
self.position = 0
elif value == 'pause':
if self.state == 'play' and self.videoview.isPlaying():
self.videoview.pause()
elif value == 'stop':
if self.videoview.isPlaying():
self.videoview.stopPlayback()
self.eos = True
self.position = -1
Logger.info('AndroidVideoView: Set state to {0}'.format(value))
def on_size(self, instance, size):
self._on_size(instance, size)
@run_on_ui_thread
def _on_size(self, instance, size):
if self._holder:
self._holder.size = size
Logger.info('AndroidVideoView: Set size to {0}'.format(size))
def on_pos(self, instance, pos):
self._on_pos(instance, pos)
@run_on_ui_thread
def _on_pos(self, instance, pos):
if self._holder:
self._holder.pos = pos
Logger.info('AndroidVideoView: Set pos to {0}'.format(pos))
# @run_on_ui_thread
# def _on_norm_image_size(self, instance, norm_image_size):
# def on_norm_image_size(self, instance, norm_image_size):
# self.size = norm_image_size
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment