Skip to content
This repository has been archived by the owner on Sep 8, 2024. It is now read-only.

feat/gui video common play #2683

Closed
wants to merge 14 commits into from
112 changes: 111 additions & 1 deletion mycroft/enclosure/gui.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,19 @@
#
""" Interface for interacting with the Mycroft gui qml viewer. """
from os.path import join

from enum import IntEnum
from mycroft.configuration import Configuration
from mycroft.messagebus.message import Message
from mycroft.util import resolve_resource_file


class GUIPlaybackStatus(IntEnum):
STOPPED = 0
PLAYING = 1
PAUSED = 2
UNDEFINED = 3


class SkillGUI:
"""SkillGUI - Interface to the Graphical User Interface

Expand All @@ -38,6 +45,7 @@ def __init__(self, skill):
self.skill = skill
self.on_gui_changed_callback = None
self.config = Configuration.get()
self.video_info = None

@property
def connected(self):
Expand All @@ -63,6 +71,42 @@ def setup_default_handlers(self):
msg_type = self.build_message_type('set')
self.skill.add_event(msg_type, self.gui_set)

# TODO can we rename this namespace to mycroft.playback.XXX ?
self.skill.add_event('mycroft.audio.service.pause',
self.__handle_gui_pause)
self.skill.add_event('mycroft.audio.service.resume',
self.__handle_gui_resume)
self.skill.add_event('mycroft.audio.service.stop',
self.__handle_gui_stop)
self.skill.add_event('mycroft.audio.service.track_info',
self.__handle_gui_track_info)
self.skill.add_event('mycroft.audio.queue_end',
self.__handle_gui_stop)
self.skill.gui.register_handler('video.media.playback.ended',
self.__handle_gui_stop)

# Audio Service bus messages
def __handle_gui_resume(self, message):
"""Resume video playback in gui"""
self.resume_video()

def __handle_gui_stop(self, message):
"""Stop video playback in gui"""
self.stop_video()

def __handle_gui_pause(self, message):
"""Pause video playback in gui"""
self.pause_video()

def __handle_gui_track_info(self, message):
"""Answer request information of current playing track.
Needed for handling stop """
if self.video_info:
self.skill.bus.emit(
message.reply('mycroft.audio.service.track_info_reply',
self.video_info))
return self.video_info

def register_handler(self, event, handler):
"""Register a handler for GUI events.

Expand Down Expand Up @@ -367,6 +411,72 @@ def release(self):
self.skill.bus.emit(Message("mycroft.gui.screen.close",
{"skill_id": self.skill.skill_id}))

def play_video(self, url, title="", repeat=None, override_idle=True,
override_animations=None):
""" Play video stream

Arguments:
url (str): URL of video source
title (str): Title of media to be displayed
repeat (boolean, int):
True: Infinitly loops the current video track
(int): Loops the video track for specified number of
times.
override_idle (boolean, int):
True: Takes over the resting page indefinitely
(int): Delays resting page for the specified number of
seconds.
override_animations (boolean):
True: Disables showing all platform skill animations.
False: 'Default' always show animations.
"""
self["playStatus"] = "play"
self["video"] = url
self["title"] = title
self["playerRepeat"] = repeat
self.video_info = {"title": title, "url": url}
self.show_page("SYSTEM_VideoPlayer.qml",
override_idle=override_idle,
override_animations=override_animations)

@property
def is_video_displayed(self):
"""Returns whether the gui is in a video playback state.
Eg if the video is paused, it would still be displayed on screen
but the video itself is not "playing" so to speak"""
return self.video_info is not None

@property
def playback_status(self):
"""Returns gui playback status,
indicates if gui is playing, paused or stopped"""
if self.__session_data.get("playStatus", -1) == "play":
return GUIPlaybackStatus.PLAYING
if self.__session_data.get("playStatus", -1) == "pause":
return GUIPlaybackStatus.PAUSED
if self.__session_data.get("playStatus", -1) == "stop":
return GUIPlaybackStatus.STOPPED
return GUIPlaybackStatus.UNDEFINED

def pause_video(self):
"""Pause video playback."""
if self.is_video_displayed:
self["playStatus"] = "pause"

def stop_video(self):
"""Stop video playback."""
# TODO detect end of media playback from gui and call this
if self.is_video_displayed:
self["playStatus"] = "stop"
self.skill.bus.emit(Message("mycroft.gui.screen.close",
{"skill_id": self.skill.skill_id}))
self.video_info = None

def resume_video(self):
"""Resume paused video playback."""
if self.__session_data.get("playStatus", "stop") == "pause":
self["playStatus"] = "play"

def shutdown(self):
"""Shutdown gui interface.

Expand Down
225 changes: 225 additions & 0 deletions mycroft/res/ui/SYSTEM_VideoPlayer.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
import QtMultimedia 5.12
import QtQuick.Layouts 1.4
import QtQuick 2.9
import QtQuick.Controls 2.12 as Controls
import org.kde.kirigami 2.10 as Kirigami
import QtQuick.Window 2.3
import QtGraphicalEffects 1.0
import Mycroft 1.0 as Mycroft
import "." as Local

Mycroft.Delegate {
id: root

property var videoSource: sessionData.video
property var videoStatus: sessionData.playStatus
property var videoRepeat: sessionData.playerRepeat
property var videoThumb: sessionData.videoThumb
property var videoTitle: sessionData.title
property bool busyIndicate: false

fillWidth: true
background: Rectangle {
color: "black"
}
leftPadding: 0
topPadding: 0
rightPadding: 0
bottomPadding: 0

onEnabledChanged: syncStatusTimer.restart()
onVideoSourceChanged: syncStatusTimer.restart()

Component.onCompleted: {
syncStatusTimer.restart()
}

Keys.onDownPressed: {
controlBarItem.opened = true
controlBarItem.forceActiveFocus()
}

onFocusChanged: {
video.forceActiveFocus();
}

onVideoRepeatChanged: {
if(typeof videoRepeat !== "undefined" && typeof videoRepeat == "boolean"){
if(videoRepeat){
video.loops = MediaPlayer.Infinite
video.flushMode = VideoOutput.LastFrame
}
} else if(typeof videoRepeat !== "undefined" && typeof videoRepeat == "number"){
if(videoRepeat > 1){
video.loops = videoRepeat
video.flushMode = VideoOutput.LastFrame
}
} else {
video.loops = 1
video.flushMode = VideoOutput.EmptyFrame
}
}

onVideoStatusChanged: {
switch(videoStatus){
case "stop":
video.stop();
break;
case "pause":
video.pause()
break;
case "play":
video.play()
delay(6000, function() {
infomationBar.visible = false;
})
break;
}
}

Connections {
target: Window.window
onVisibleChanged: {
if(video.playbackState == MediaPlayer.PlayingState) {
video.stop()
}
}
}

Timer {
id: syncStatusTimer
interval: 0
onTriggered: {
if (enabled && videoStatus == "play") {
video.play();
} else if (videoStatus == "stop") {
video.stop();
} else {
video.pause();
}
}
}

Timer {
id: delaytimer
}

function delay(delayTime, cb) {
delaytimer.interval = delayTime;
delaytimer.repeat = false;
delaytimer.triggered.connect(cb);
delaytimer.start();
}

controlBar: Local.SeekControl {
id: seekControl
anchors {
bottom: parent.bottom
}
title: videoTitle
videoControl: video
duration: video.duration
playPosition: video.position
onSeekPositionChanged: video.seek(seekPosition);
z: 1000
}

Item {
id: videoRoot
anchors.fill: parent

Rectangle {
id: infomationBar
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
visible: false
color: Qt.rgba(Kirigami.Theme.backgroundColor.r, Kirigami.Theme.backgroundColor.g, Kirigami.Theme.backgroundColor.b, 0.6)
implicitHeight: vidTitle.implicitHeight + Kirigami.Units.largeSpacing * 2
z: 1001

onVisibleChanged: {
delay(15000, function() {
infomationBar.visible = false;
})
}

Controls.Label {
id: vidTitle
visible: true
maximumLineCount: 2
wrapMode: Text.Wrap
anchors.left: parent.left
anchors.leftMargin: Kirigami.Units.largeSpacing
anchors.verticalCenter: parent.verticalCenter
text: videoTitle
z: 100
}
}

Video {
id: video
anchors.fill: parent
focus: true
autoLoad: true
autoPlay: false
loops: 1
source: videoSource

Keys.onReturnPressed: {
video.playbackState == MediaPlayer.PlayingState ? video.pause() : video.play()
}

Keys.onDownPressed: {
controlBarItem.opened = true
controlBarItem.forceActiveFocus()
}

MouseArea {
anchors.fill: parent
onClicked: {
controlBarItem.opened = !controlBarItem.opened
}
}

onStatusChanged: {
console.log(status)
if(status == MediaPlayer.EndOfMedia) {
triggerGuiEvent("video.media.playback.ended", {})
busyIndicatorPop.enabled = false
}
if(status == MediaPlayer.Loading) {
busyIndicatorPop.visible = true
busyIndicatorPop.enabled = true
}
if(status == MediaPlayer.Loaded || status == MediaPlayer.Buffered){
busyIndicatorPop.visible = false
busyIndicatorPop.enabled = false
}
}

Rectangle {
id: busyIndicatorPop
width: parent.width
height: parent.height
color: Qt.rgba(0, 0, 0, 0.2)
visible: false
enabled: false

Controls.BusyIndicator {
id: busyIndicate
running: busyIndicate
anchors.centerIn: parent
}

onEnabledChanged: {
if(busyIndicatorPop.enabled){
busyIndicate.running = true
} else {
busyIndicate.running = false
}
}
}
}
}
}
Loading