2

I am trying to create a video playing app to be run on an single board computer (specifically an OrangePi One/PC using armbian). I have been having issues creating smooth video with the ffpyplayer, the videoplayer used by kivy.

Due to this I have decided to simply run the app which generates button controls in front of the video and attempt to make the background of the app clear so that both the video and kivy buttons are visible. as recommended in the linked post : Kivy Video Player is not working on Raspberry 3B+

I have seen a similar thing done in: https://github.com/kivy/kivy/pull/5252 I am testing the programs on a windows computer before uploading to the single board computers and I have been unable to successfully create a blank background. I have been mainly trying to modify the .kv file however I am unable to find any settings that can adjust to create the desired result.

For simplicity sake I am uploading a shorter code which is layed out in the exact same way as my main code:

Kivy code: my.kv

#:kivy 1.0 
<MyGrid>

    background_color: 0, 0, 0, 0 # only creates a black colour
    #opacity: 0.5 #just affects the widgets not the background

    canvas.before:
        Color:
            rgba: 0, 0, 0, 1
        Rectangle:
            pos: self.pos
            size: self.size


    #variable name: ID
    name: name #declare global variables
    email: email 

    GridLayout:
        cols:1
        size: root.width-200, root.height-200 #make the widget fit the screen and then minus a border from it
        pos: 100, 100 #offset the position to compensate for the boarder

        GridLayout: 
            cols: 2

            Label:
                text: "Name: "

            TextInput:
                id: name
                multiline:False

            Label:
                text: "email: "

            TextInput:
                id: email 
                multiline: False

        Button:
            text: "submit"
            on_press: root.btn() #if there was an event. ie button was pressed

Python code:

    import kivy
    from kivy.app import App 
    from kivy.uix.label import Label
    from kivy.uix.gridlayout import GridLayout
    from kivy.uix.textinput import TextInput
    from kivy.network.urlrequest import UrlRequest
    from kivy.uix.boxlayout import BoxLayout
    from kivy.uix.button import Button
    from kivy.uix.widget import Widget
    from kivy.properties import ObjectProperty
    from kivy.core.window import Window
    
    class MyGrid(Widget): #creates apps layout and contents
            #declaire global variables to pass to and from .kv file. note the variables names must be the same between the 2 files 
            name = ObjectProperty(None) #initialise as none and then after reading the .kv file it will populate it 
            email = ObjectProperty(None)
            
            def btn(self): #function btn which occurs. it needs to be in here 
                print ("name: ", self.name.text, " email: ", self.email.text)
                self.name.text = ""
                self.email.text = ""
    
    class MyApp(App): #creates the app
        def build(self):
            return MyGrid()
    
    if __name__ == '__main__': #runs the app 
      

  MyApp().run()

The attached photo shows the app running with the video using ffpyplayer and is more or less the end goal. The objective is that the green colour would be the desktop. Example Image.

any guidance would be hugely appreciated!

foch
  • 31
  • 1
  • 5
  • Does the example that you linked work for you? That seems like the obvious thing to test, since it must have worked during the original PR. – inclement Aug 05 '20 at 22:11
  • I am not aware of any way to make a Kivy App transparent, but have you considered just making the App small so that it only covers a portion of the video where you want the Buttons? – John Anderson Aug 06 '20 at 00:24
  • @inclement unfortunately not setting the background opacity to 0 makes the background a dark grey colour rather than the desktop background. The second example is able to remove part of the screen however I am struggling to understand how to manipulate it to my needs. As far as I can tell you use a vectorised image (like a png) as a template for the program and you select a colour from that which will be see through. If i am correct I'm not sure how to make an appropriate image. – foch Aug 06 '20 at 15:43
  • @JohnAnderson good idea! i'll give it a shot, I was considering minimising the app after a while so the buttons aren't constantly on the screen in place of my original plan of making everything transparent. – foch Aug 06 '20 at 15:52

1 Answers1

1

Just to give an answer to my question in case someone has a similar issue.

Using Kivy turned out not to be viable for me for 2 reasons:

  1. While it is possible to make a kivy app transparent as in the example https://github.com/kivy/kivy/pull/5252 it requires a template image to be done. As I wanted my application to run on different sized screens and I don't know anything about generating images I decided against using this method.

  2. Making the button app small so that it covers only a portion of the video worked well. This became problematic as the video runs using another program meaning the video would open in front of the app and I would loose control until I clicked on the app specifically. It is possible to overcome this on Windows by starting the video and then delay the generation of the buttons or by minimise and reopen the app after the video screen was created. Unfortunately this method did not translate to the single board computer as to maximise video playback performance I am not using a desktop environment, only the X Window System (X11) and even with a basic window manager I ran into a lot of difficulties minimising and maximising windows.

My solution was to move to Tkinter as it has the ability to force a window to be in front and can be made transparent. Below is the equivalent code in Tkinter, while its not completed it has the functionality I mentioned in the original question:

from functools import partial
import tkinter as tk
import threading
import time
import vlc
global videoscreen_button_num
videoscreen_button_num = 0

global button_timer
global show_button
button_timer = 0
show_button = False


class video_window:
    def __init__(self, master, channel): #create the windows
    self.master = master
    self.frame = tk.Frame(self.master)

    reset_encoder = tk.Button(self.frame, text = 'Reset Encoder', width = 25, command = self.close_windows) #create buttons to control the video
    reset_video = tk.Button(self.frame, text = 'Refresh Video', width = 25, command = self.close_windows)
    return_button = tk.Button(self.frame, text = 'Return to Main Menu', width = 25, command = self.close_windows)

    return_button.grid(row=0, column=0)
    reset_video.grid(row=0, column=1)
    reset_encoder.grid(row=0, column=2)

    unselected_colour = "SteelBlue1" 
    selected_colour = "RoyalBlue1"

    return_button.config(background = selected_colour)
    reset_video.config(background = unselected_colour)
    reset_encoder.config(background = unselected_colour)

    self.frame.pack()

    self.video_button_ids = [] 
    self.video_button_ids.append(reset_encoder)
    self.video_button_ids.append(reset_video)
    self.video_button_ids.append(return_button)

    Instance = vlc.Instance() #'--verbose 2'.split())
    self.player = Instance.media_player_new()
    Media = Instance.media_new(channel)
    Media.get_mrl()
    self.player.set_media(Media)
    #player.set_fullscreen(False)
    self.player.play()
    self.player.set_fullscreen(True)

    master.attributes('-topmost', 'true') #force the window to be infront 

    time.sleep(5) #give player time to load before spawning the control buttons

    master.bind('<Key>', partial(self.press_button) )

    master.mainloop()

    self.timed_opacity_thread = threading.Thread(target=self.timed_opacity) #make a thread which manage when the opacity of the buttons will turn on and off
    self.timed_opacity_thread.start()

def press_button(self, event): #tells the program what to do if a button is pressed 
    print("pressed button in video screen")

    if show_button == True: #if the opacity is set to be visable simple extend the time 
        global button_timer
        button_timer = time.perf_counter()
    else: # if the opacity is off turn it back on and place it into a thread to that other tasks can be performed at the same time.
        self.timed_opacity_thread = threading.Thread(target=self.timed_opacity)
        self.timed_opacity_thread.start()

    global videoscreen_button_num
    old_button_num = videoscreen_button_num
    print("button pressed", event.keycode)
    if event.keycode == 40 : #up button
        if videoscreen_button_num == 2:
            videoscreen_button_num = 0
        else:
            videoscreen_button_num = videoscreen_button_num + 1

    if event.keycode == 38 : #down
        if videoscreen_button_num == 0 :
            videoscreen_button_num = 2
        else:
            videoscreen_button_num = videoscreen_button_num - 1
        
    if event.keycode == 37: #right
        if videoscreen_button_num == 2:
            videoscreen_button_num = 0
        else:
            videoscreen_button_num = videoscreen_button_num + 1 

    if event.keycode == 39 : #left
        if videoscreen_button_num == 0 :
            videoscreen_button_num = 2
        else:
            videoscreen_button_num = videoscreen_button_num - 1    
    
    if event.keycode == 13: #enter
        print("pressed enter")
        self.video_button_ids[videoscreen_button_num].invoke()

    print("old button = ", old_button_num)
    print ("new button = ", videoscreen_button_num)

    print ("old object = ", self.video_button_ids[old_button_num])
    print ("new object = ", self.video_button_ids[videoscreen_button_num])

    unselected_colour = "SteelBlue1" 
    selected_colour = "RoyalBlue1"

    self.video_button_ids[old_button_num].config(background = unselected_colour)
    self.video_button_ids[videoscreen_button_num].config(background = selected_colour)

    print("")

def timed_opacity(self): #make the buttons fade when no user input is detected for a while and then reappear when a user input is detected
    self.master.attributes('-alpha', 1)
    
    global button_timer 
    global show_button
    button_timer = time.perf_counter()
    show_button = True
    while time.perf_counter() - button_timer < 10:
        pass

    self.master.attributes('-alpha', .5)
    show_button = False

def close_windows(self):
    self.player.stop()
    global show_button 

    self.master.destroy() #get rid of window, app still runs, not sure how to kill app 
    
def refresh_video(self):
    print("refresh video funtion")
    self.player.Refresh()

def main(): 
    root = tk.Tk()
    app = video_window(root, 'rtsp://192.168.8.20/4') #the location of the video you wish to play
    root.mainloop()

if __name__ == '__main__':
    main()

Hopefully this might help someone out

foch
  • 31
  • 1
  • 5