on xenial help sign in
Media Player in dash
commit148f507a6b5c440148a2819054fdc6ec14ace64b authorNick Dedekind <nick.dedekind@canonical.com> dateThu 21 May 2015, 17:45 (11 years ago) parenta9b5255
18 files changed+1156−420
qml/Components/MediaServices/MediaServices.qml+60−34 qml/Components/MediaServices/MediaServicesControls.qml+74−75 qml/Components/MediaServices/MediaServicesHeader.qml+1 qml/Components/MediaServices/VideoPlayer.qml+18−24 qml/Components/MediaServices/VideoPlayerControls.qml+6−38 qml/Dash/Previews/PreviewInlineVideo.qml+4−19 tests/mocks/QtMultimedia/CMakeLists.txt+8−2 tests/mocks/QtMultimedia/VideoSurface.qml+92 tests/mocks/QtMultimedia/audio.cpp−117 tests/mocks/QtMultimedia/audio.h−90 tests/mocks/QtMultimedia/mediaplayer.cpp+288 tests/mocks/QtMultimedia/mediaplayer.h+195 tests/mocks/QtMultimedia/plugin.cpp+7−2 tests/mocks/QtMultimedia/videooutput.cpp+111 tests/mocks/QtMultimedia/videooutput.h+36 tests/qmltests/CMakeLists.txt+2−1 tests/qmltests/Components/tst_MediaServices.qml+204 tests/qmltests/Dash/Previews/tst_PreviewInlineVideo.qml+50−18
qml/Components/MediaServices/MediaServices.qml+60−34
@@ -22,21 +22,30 @@ FocusScope {
2222 id: root
2323 property var sourceData
2424 property string context: ""
25− property bool fullscreen: false
2625 property list<Action> actions
26+ property Item rootItem: QuickUtils.rootItem(root)
2727
2828 property alias header: headerContent.item
2929 property alias content: contentLoader.item
3030 property alias footer: footerContent.item
31+ property alias fullscreen: priv.fullscreen
32+
33+ signal close();
3134
3235 implicitHeight: {
3336 if (fullscreen && parent) {
3437 return parent.height;
3538 }
36− console.log(contentLoader.height, fullscreen, parent)
3739 return contentLoader.height
3840 }
39− height: implicitHeight
41+ Behavior on implicitHeight {
42+ enabled: fullscreen
43+ UbuntuNumberAnimation { duration: UbuntuAnimation.FastDuration }
44+ }
45+
46+ onFullscreenChanged: {
47+ if (!fullscreen) rotationAction.checked = false;
48+ }
4049
4150 OrientationHelper {
4251 id: orientationHelper
@@ -57,6 +66,12 @@ FocusScope {
5766 }
5867 ]
5968
69+ Rectangle {
70+ anchors.fill: contentLoader
71+ color: "#1B1B1B"
72+ opacity: 0.85
73+ }
74+
6075 Loader {
6176 id: contentLoader
6277
@@ -72,8 +87,15 @@ FocusScope {
7287 id: videoComponent
7388 VideoPlayer {
7489 id: player
90+ objectName: "videoPlayer"
91+
7592 width: orientationHelper.width
7693 height: fullscreen ? orientationHelper.height : implicitHeight
94+ Behavior on height {
95+ enabled: !fullscreen
96+ UbuntuNumberAnimation { duration: UbuntuAnimation.FastDuration }
97+ }
98+
7799 fixedHeight: fullscreen
78100 orientation: orientationHelper.state == "portrait" ? Qt.PortraitOrientation : Qt.LandscapeOrientation
79101
@@ -115,7 +137,7 @@ FocusScope {
115137 Loader {
116138 id: headerContent
117139 anchors {
118− top: parent.top
140+ top: contentLoader.top
119141 left: parent.left
120142 right: parent.right
121143 topMargin: -units.gu(6)
@@ -136,7 +158,7 @@ FocusScope {
136158 MediaServicesHeader {
137159 onGoPrevious: {
138160 rotationAction.checked = false;
139− fullscreen = false;
161+ root.close();
140162 }
141163
142164 component: {
@@ -151,7 +173,6 @@ FocusScope {
151173
152174 // If we interact with the bar, reset the hide timer.
153175 MouseArea {
154− z: 1
155176 anchors.fill: parent
156177 onPressed: {
157178 mouse.accepted = false
@@ -165,7 +186,7 @@ FocusScope {
165186 anchors {
166187 left: parent.left
167188 right: parent.right
168− bottom: parent.bottom
189+ bottom: contentLoader.bottom
169190 bottomMargin: -units.gu(7)
170191 }
171192 height: units.gu(7)
@@ -187,6 +208,7 @@ FocusScope {
187208 id: videoControlsComponent
188209 VideoPlayerControls {
189210 id: controls
211+ objectName: "videoControls"
190212
191213 viewAction: rotationAction.enabled ? rotationAction : fullscreenAction
192214 userActions: root.actions
@@ -203,7 +225,7 @@ FocusScope {
203225 }
204226
205227 Binding {
206− target: controlStates
228+ target: priv
207229 property: "forceControlsShown"
208230
209231 value: (fullscreen && mediaPlayer.playbackState === MediaPlayer.StoppedState) ||
@@ -218,7 +240,9 @@ FocusScope {
218240 Binding {
219241 target: header
220242 property: "title"
221− value: controls.mediaPlayer.metaData.title
243+ value: controls.mediaPlayer.metaData.title !== undefined ?
244+ controls.mediaPlayer.metaData.title :
245+ controls.mediaPlayer.source.toString().replace(/^.*[\\\/]/, '')
222246 when: header != null
223247 }
224248 }
@@ -237,14 +261,10 @@ FocusScope {
237261 }
238262
239263 StateGroup {
240− id: controlStates
241− property bool controlTimerActive: false
242− property bool forceControlsShown: false
243−
244264 states: [
245265 State {
246266 name: "controlsShown"
247− when: controlStates.forceControlsShown || controlStates.controlTimerActive
267+ when: priv.forceControlsShown || priv.controlTimerActive
248268 PropertyChanges {
249269 target: footerContent
250270 anchors.bottomMargin: 0
@@ -286,32 +306,45 @@ FocusScope {
286306 ]
287307 }
288308
309+ StateGroup {
310+ states: [
311+ State {
312+ name: "fullscreen"
313+ when: priv.fullscreen
314+ PropertyChanges { target: root; parent: rootItem }
315+ PropertyChanges { target: fullscreenAction; iconName: "view-restore" }
316+ }
317+ ]
318+ }
319+
320+ QtObject {
321+ id: priv
322+
323+ property bool fullscreen: false
324+ property bool controlTimerActive: false
325+ property bool forceControlsShown: false
326+ }
327+
289328 Timer {
290329 id: controlHideTimer
330+ objectName: "controlHideTimer"
291331 interval: 4000
292332 running: false
293333
294334 onRunningChanged: {
295335 if (running) {
296− controlStates.controlTimerActive = true;
336+ priv.controlTimerActive = true;
297337 }
298338 }
299− onTriggered: controlStates.controlTimerActive = false;
339+ onTriggered: priv.controlTimerActive = false;
300340 }
301341
302342 Action {
303343 id: fullscreenAction
304344 enabled: rotationAction.enabled === false
305− iconName: root.fullscreen ? "view-restore" : "view-fullscreen"
306−
307− onTriggered: {
308− root.fullscreen = !root.fullscreen
309− if (root.fullscreen) {
310− console.log("fullscreen: fullscreen")
311− } else {
312− console.log("fullscreen: windowed")
313− }
314− }
345+ iconName: "view-fullscreen"
346+
347+ onTriggered: priv.fullscreen = !priv.fullscreen
315348 }
316349
317350 Action {
@@ -320,13 +353,6 @@ FocusScope {
320353 iconName: "orientation-lock"
321354
322355 property bool checked: false
323− onTriggered: {
324− checked = !checked;
325− if (checked) {
326− console.log("rotation: landscape")
327− } else {
328− console.log("rotation: portrait")
329− }
330− }
356+ onTriggered: checked = !checked
331357 }
332358}
qml/Components/MediaServices/MediaServicesControls.qml+74−75
@@ -1,6 +1,6 @@
11import QtQuick 2.0
22import QtQuick.Layouts 1.1
3−import QtMultimedia 5.4
3+import QtMultimedia 5.0
44import Ubuntu.Components 1.3
55import Ubuntu.Components.ListItems 1.0 as ListItem
66import Ubuntu.Components.Themes 1.0
@@ -72,6 +72,7 @@ Item {
7272 }
7373
7474 AbstractButton {
75+ objectName: "viewActionButton"
7576 Layout.preferredWidth: units.gu(3)
7677 Layout.preferredHeight: units.gu(3)
7778 Layout.alignment: Qt.AlignVCenter
@@ -91,90 +92,88 @@ Item {
9192 Component {
9293 id: userActionsPopup
9394
94− Rectangle {
95− id: popup
96− color: "#1B1B1B"
97− width: userActionsColumn.width
98− height: userActionsColumn.height
95+ Rectangle {
96+ id: popup
97+ color: "#1B1B1B"
98+ width: userActionsColumn.width
99+ height: userActionsColumn.height
99100
100− InverseMouseArea {
101− id: eventGrabber
102− acceptedButtons: Qt.LeftButton | Qt.MiddleButton | Qt.RightButton
103− anchors.fill: popup
104− propagateComposedEvents: false
105− onWheel: wheel.accepted = true
101+ InverseMouseArea {
102+ id: eventGrabber
103+ acceptedButtons: Qt.LeftButton | Qt.MiddleButton | Qt.RightButton
104+ anchors.fill: popup
105+ propagateComposedEvents: false
106+ onWheel: wheel.accepted = true
106107
107− onPressed: popup.destroy()
108− }
108+ onPressed: popup.destroy()
109+ }
109110
110− Column {
111− id: userActionsColumn
112− spacing: units.gu(1)
113−
114− width: units.gu(31)
115− onHeightChanged: console.log("height", height)
116−
117− Repeater {
118− id: actionRepeater
119− model: userActions
120− AbstractButton {
121− action: modelData
122−
123− onClicked: popup.destroy()
124−
125− implicitHeight: units.gu(4) + bottomDividerLine.height
126− width: parent ? parent.width : units.gu(31)
127−
128− Rectangle {
129− visible: parent.pressed
130− anchors {
131− left: parent.left
132− right: parent.right
133− top: parent.top
134− }
135− height: parent.height - bottomDividerLine.height
136− opacity: 0.5
137− }
111+ Column {
112+ id: userActionsColumn
113+ spacing: units.gu(1)
114+ width: units.gu(31)
115+
116+ Repeater {
117+ id: actionRepeater
118+ model: userActions
119+ AbstractButton {
120+ action: modelData
121+
122+ onClicked: popup.destroy()
138123
139− Icon {
140− id: actionIcon
141− visible: "" !== action.iconSource
142− source: action.iconSource
143− color: "#F3F3E7"
144− anchors {
145− verticalCenter: parent.verticalCenter
146− verticalCenterOffset: units.dp(-1)
147− left: parent.left
148− leftMargin: units.gu(2)
149− }
150− width: units.gu(2)
151− height: units.gu(2)
152− opacity: action.enabled ? 1.0 : 0.5
124+ implicitHeight: units.gu(4) + bottomDividerLine.height
125+ width: parent ? parent.width : units.gu(31)
126+
127+ Rectangle {
128+ visible: parent.pressed
129+ anchors {
130+ left: parent.left
131+ right: parent.right
132+ top: parent.top
153133 }
134+ height: parent.height - bottomDividerLine.height
135+ opacity: 0.5
136+ }
154137
155− Label {
156− anchors {
157− verticalCenter: parent.verticalCenter
158− verticalCenterOffset: units.dp(-1)
159− left: actionIcon.visible ? actionIcon.right : parent.left
160− leftMargin: units.gu(2)
161− right: parent.right
162− }
163− // In the tabs overflow panel there are no icons, and the font-size
164− // is medium as opposed to the small font-size in the actions overflow panel.
165− fontSize: actionIcon.visible ? "small" : "medium"
166− elide: Text.ElideRight
167− text: action.text
168− color: "#F3F3E7"
169− opacity: action.enabled ? 1.0 : 0.5
138+ Icon {
139+ id: actionIcon
140+ visible: "" !== action.iconSource
141+ source: action.iconSource
142+ color: "#F3F3E7"
143+ anchors {
144+ verticalCenter: parent.verticalCenter
145+ verticalCenterOffset: units.dp(-1)
146+ left: parent.left
147+ leftMargin: units.gu(2)
170148 }
149+ width: units.gu(2)
150+ height: units.gu(2)
151+ opacity: action.enabled ? 1.0 : 0.5
152+ }
171153
172− ListItem.ThinDivider {
173− id: bottomDividerLine
174− anchors.bottom: parent.bottom
175− visible: index !== actionRepeater.count - 1
154+ Label {
155+ anchors {
156+ verticalCenter: parent.verticalCenter
157+ verticalCenterOffset: units.dp(-1)
158+ left: actionIcon.visible ? actionIcon.right : parent.left
159+ leftMargin: units.gu(2)
160+ right: parent.right
176161 }
162+ // In the tabs overflow panel there are no icons, and the font-size
163+ // is medium as opposed to the small font-size in the actions overflow panel.
164+ fontSize: actionIcon.visible ? "small" : "medium"
165+ elide: Text.ElideRight
166+ text: action.text
167+ color: "#F3F3E7"
168+ opacity: action.enabled ? 1.0 : 0.5
169+ }
170+
171+ ListItem.ThinDivider {
172+ id: bottomDividerLine
173+ anchors.bottom: parent.bottom
174+ visible: index !== actionRepeater.count - 1
177175 }
176+ }
178177 }
179178 }
180179 }
qml/Components/MediaServices/MediaServicesHeader.qml+1
@@ -29,6 +29,7 @@ Item {
2929 // eater
3030 AbstractButton {
3131 id: navigationButton
32+ objectName: "navigationButton"
3233 Layout.preferredWidth: units.gu(3)
3334 Layout.preferredHeight: units.gu(3)
3435 Layout.alignment: Qt.AlignVCenter
qml/Components/MediaServices/VideoPlayer.qml+18−24
@@ -23,7 +23,7 @@ import "../../Components"
2323Item {
2424 id: root
2525
26− property url screenshot: image.source
26+ property alias screenshot: image.source
2727 property alias mediaPlayer: videoOutput.source
2828 property int orientation: Qt.PortraitOrientation
2929 property bool fixedHeight: false
@@ -38,15 +38,6 @@ Item {
3838 signal clicked
3939 signal positionChanged
4040
41− Rectangle {
42− anchors.fill: root
43−// anchors.leftMargin: units.gu(2)
44−// anchors.rightMargin: units.gu(2)
45− color: "#1B1B1B"
46− opacity: 0.85
47− visible: mediaPlayer.playbackState !== MediaPlayer.StoppedState
48− }
49−
5041 Item {
5142 id: content
5243 anchors {
@@ -54,7 +45,7 @@ Item {
5445 right: parent.right
5546 verticalCenter: parent.verticalCenter
5647 }
57− height: root.orientation == Qt.LandscapeOrientation || fixedHeight ? root.height : childrenRect.height;
48+ height: root.orientation == Qt.LandscapeOrientation || fixedHeight ? root.height : Math.max(image.height, videoOutput.height);
5849
5950 LazyImage {
6051 id: image
@@ -66,25 +57,25 @@ Item {
6657 anchors.verticalCenter: parent.verticalCenter
6758 scaleTo: "width"
6859 initialHeight: width * 10 / 16
69− visible: mediaPlayer.playbackState === MediaPlayer.StoppedState
60+ visible: !mediaPlayer || mediaPlayer.playbackState === MediaPlayer.StoppedState
7061 }
7162
7263 VideoOutput {
7364 id: videoOutput
74− anchors {
75− left: parent.left
76− right: parent.right
77− }
78− anchors.verticalCenter: parent.verticalCenter
65+ anchors.centerIn: parent
66+
67+ width: root.width
7968 height: {
80− if (root.orientation == Qt.LandscapeOrientation || fixedHeight) {
69+ if (fixedHeight) {
8170 return root.height;
8271 }
83− return (mediaPlayer.metaData.resolution.height / mediaPlayer.metaData.resolution.width ) * width;
72+ return mediaPlayer && mediaPlayer.metaData.resolution !== undefined ?
73+ (mediaPlayer.metaData.resolution.height / mediaPlayer.metaData.resolution.width ) * width :
74+ image.height;
8475 }
8576
8677 source: mediaPlayer
87− visible: mediaPlayer.playbackState !== MediaPlayer.StoppedState
78+ visible: mediaPlayer && mediaPlayer.playbackState !== MediaPlayer.StoppedState || false
8879 }
8980
9081// Loader {
@@ -196,7 +187,7 @@ Item {
196187 anchors.centerIn: content
197188 width: bigButton ? units.gu(10) : units.gu(8)
198189 height: width
199− visible: mediaPlayer.playbackState !== MediaPlayer.PlayingState
190+ visible: mediaPlayer && mediaPlayer.playbackState !== MediaPlayer.PlayingState || false
200191 color: "#1B1B1B"
201192 opacity: 0.85
202193 radius: width/2
@@ -213,14 +204,17 @@ Item {
213204
214205 ActivityIndicator {
215206 anchors.centerIn: content
216− running: mediaPlayer.status === MediaPlayer.Stalled ||
217− (mediaPlayer.playbackState === MediaPlayer.PlayingState && (mediaPlayer.status === MediaPlayer.Loading || mediaPlayer.status === MediaPlayer.Loaded))
207+ running: {
208+ if (!mediaPlayer) return false;
209+ return mediaPlayer.status === MediaPlayer.Stalled ||
210+ (mediaPlayer.playbackState === MediaPlayer.PlayingState && mediaPlayer.status === MediaPlayer.Loading);
211+ }
218212 }
219213
220214 MouseArea {
221215 id: contentMouseArea
222216 anchors.fill: content
223− hoverEnabled: mediaPlayer.playbackState !== MediaPlayer.StoppedState
217+ hoverEnabled: mediaPlayer && mediaPlayer.playbackState !== MediaPlayer.StoppedState || false
224218
225219 onClicked: root.clicked()
226220 onPositionChanged: root.positionChanged()
qml/Components/MediaServices/VideoPlayerControls.qml+6−38
@@ -6,7 +6,7 @@ import Ubuntu.Components.Themes 1.0
66
77MediaServicesControls {
88 id: root
9− property alias mediaPlayer: _mediaPlayer
9+ readonly property alias mediaPlayer: _mediaPlayer
1010 property bool interacting: false
1111
1212 QtObject {
@@ -35,7 +35,7 @@ MediaServicesControls {
3535 slider.value = mediaPlayer.position;
3636 slider.valueGuard = false;
3737 if (!slider.pressed) {
38− positionLabel.text = priv.formatTime(_mediaPlayer.position);
38+ positionLabel.text = priv.formatTime(mediaPlayer.position);
3939 }
4040 }
4141 }
@@ -57,7 +57,7 @@ MediaServicesControls {
5757 fontSize: "x-small"
5858 color: "#F3F3E7"
5959
60− text: priv.formatTime(_mediaPlayer.position)
60+ text: priv.formatTime(mediaPlayer.position)
6161 }
6262
6363 Slider {
@@ -96,7 +96,7 @@ MediaServicesControls {
9696 mediaPlayer.pause();
9797 } else {
9898
99− positionLabel.text = priv.formatTime(_mediaPlayer.position);
99+ positionLabel.text = priv.formatTime(mediaPlayer.position);
100100 if (wasPlaying) {
101101 mediaPlayer.play();
102102 }
@@ -118,44 +118,12 @@ MediaServicesControls {
118118 fontSize: "x-small"
119119 color: "#F3F3E7"
120120
121− text: {
122− var pos = (mediaPlayer.duration/1000).toFixed(0)
123−
124− var m = Math.floor(pos/60);
125− var ss = pos % 60;
126− if (ss >= 10) {
127− return m + ":" + ss;
128− } else {
129− return m + ":0" + ss;
130− }
131− }
121+ text: priv.formatTime(mediaPlayer.duration)
132122 }
133123 }
134124
135− function getPlaybackState(playbackState) {
136− if (playbackState === MediaPlayer.PlayingState) return "PlayingState";
137− else if (playbackState === MediaPlayer.PausedState) return "PausedState";
138− else if (playbackState === MediaPlayer.StoppedState) return "StoppedState";
139− return "";
140− }
141−
142− function getPlayerStatus(status) {
143− if (status === MediaPlayer.NoMedia) return "NoMedia";
144− else if (status === MediaPlayer.Loading) return "Loading";
145− else if (status === MediaPlayer.Loaded) return "Loaded";
146− else if (status === MediaPlayer.Buffering) return "Buffering";
147− else if (status === MediaPlayer.Stalled) return "Stalled";
148− else if (status === MediaPlayer.Buffered) return "Buffered";
149− else if (status === MediaPlayer.EndOfMedia) return "EndOfMedia";
150− else if (status === MediaPlayer.InvalidMedia) return "InvalidMedia";
151− else if (status === MediaPlayer.UnknownStatus) return "UnknownStatus";
152− return "";
153− }
154−
155125 MediaPlayer {
156126 id: _mediaPlayer
157−
158− onPlaybackStateChanged: console.log("PLAYBACK STATE", getPlaybackState(playbackState));
159− onStatusChanged: console.log("PLAYER STATUS", getPlayerStatus(status));
127+ objectName: "mediaPlayer"
160128 }
161129}
qml/Dash/Previews/PreviewInlineVideo.qml+4−19
@@ -32,7 +32,7 @@ PreviewWidget {
3232 implicitWidth: units.gu(35)
3333 implicitHeight: services.height
3434
35− property Item rootItem: QuickUtils.rootItem(root)
35+ property alias rootItem: services.rootItem
3636
3737 MediaServices {
3838 id: services
@@ -42,30 +42,15 @@ PreviewWidget {
4242 sourceData: widgetData
4343 fullscreen: false
4444
45+ onClose: fullscreen = false
46+
4547 actions: [
4648 Action {
4749 text: i18n.tr("Share")
4850 iconSource: "image://theme/share"
49− onTriggered: {
50− console.log("ACTION Share")
51− sharePicker.visible = true
52− }
53− }
54− ]
55−
56− states: [
57− State {
58− name: "fullscreen"
59− when: services.fullscreen
60− ParentChange { target: services; parent: rootItem; x: 0; y: 0; width: rootItem.width; height: rootItem.height}
51+ onTriggered: sharePicker.visible = true
6152 }
6253 ]
63−
64− transitions: Transition {
65− ParentAnimation {
66− UbuntuNumberAnimation { properties: "x,y,width,height" }
67− }
68− }
6954 }
7055
7156 Component {
tests/mocks/QtMultimedia/CMakeLists.txt+8−2
@@ -1,8 +1,14 @@
1+include_directories(
2+ ${CMAKE_CURRENT_SOURCE_DIR}
3+ ${CMAKE_CURRENT_BINARY_DIR}
4+)
5+
16add_library(QtMultimedia-qml MODULE
27 plugin.cpp
3− audio.cpp
8+ mediaplayer.cpp
9+ videooutput.cpp
410 )
511
6−qt5_use_modules(QtMultimedia-qml Qml)
12+qt5_use_modules(QtMultimedia-qml Qml Quick)
713
814add_unity8_mock(QtMultimedia 5.0 QtMultimedia TARGETS QtMultimedia-qml)
tests/mocks/QtMultimedia/VideoSurface.qmlnew file+92
@@ -0,0 +1,92 @@
1+import QtQuick 2.0
2+import Ubuntu.Components 1.3
3+import QtMultimedia 5.0
4+
5+Rectangle {
6+ id: root
7+ property int position: 0
8+ property int playbackState: 0
9+ property var resolution: { "width": 16, "height": 9 }
10+
11+ anchors.centerIn: parent
12+ color: "black"
13+
14+ // scale to the parent window.
15+ implicitWidth: parent ? aspectScale(Qt.size(resolution.width, resolution.height), Qt.size(parent.width, parent.height)).width : 160
16+ implicitHeight: parent ? aspectScale(Qt.size(resolution.width, resolution.height), Qt.size(parent.width, parent.height)).height : 90
17+
18+ function aspectScale(fromSize, toSize) {
19+ var rw = toSize.height * fromSize.width / fromSize.height;
20+ var useHeight = (rw <= toSize.width);
21+
22+ if (useHeight) {
23+ return Qt.size(rw, toSize.height);
24+ } else {
25+ return Qt.size(toSize.width,
26+ toSize.width * (fromSize.height / fromSize.width));
27+ }
28+ }
29+
30+ GridView {
31+ anchors.fill: parent
32+ model: (parent.width * parent.height) / units.gu(10)
33+ clip: true
34+
35+ delegate: Item {
36+ width: units.gu(10)
37+ height: units.gu(10)
38+
39+ Rectangle {
40+ id: rect
41+ color: "red"
42+ anchors.fill: parent
43+ anchors.margins: units.gu(2)
44+
45+ Connections {
46+ target: root
47+ onPositionChanged: {
48+ rect.rotation = position/100
49+ }
50+ }
51+ }
52+ }
53+ }
54+
55+ Column {
56+ anchors {
57+ left: parent.left;
58+ right: parent.right
59+ verticalCenter: parent.verticalCenter
60+ }
61+ height: units.gu(10)
62+
63+ Label {
64+ anchors.horizontalCenter: parent.horizontalCenter
65+ text: {
66+ var pos = (position/1000).toFixed(0)
67+
68+ var m = Math.floor(pos/60);
69+ var ss = pos % 60;
70+ if (ss >= 10) {
71+ return m + ":" + ss;
72+ } else {
73+ return m + ":0" + ss;
74+ }
75+ }
76+ fontSize: "x-large"
77+ }
78+
79+ Label {
80+ anchors.horizontalCenter: parent.horizontalCenter
81+ text: getPlaybackState(playbackState)
82+ fontSize: "x-large"
83+ }
84+ }
85+
86+ function getPlaybackState(playbackState) {
87+ if (playbackState === MediaPlayer.PlayingState) return "Playing";
88+ else if (playbackState === MediaPlayer.PausedState) return "Paused";
89+ else if (playbackState === MediaPlayer.StoppedState) return "Stopped";
90+ return "";
91+ }
92+}
tests/mocks/QtMultimedia/audio.cppdeleted−117
@@ -1,117 +0,0 @@
1−/*
2− * Copyright (C) 2013 Canonical, Ltd.
3− *
4− * This program is free software; you can redistribute it and/or modify
5− * it under the terms of the GNU General Public License as published by
6− * the Free Software Foundation; version 3.
7− *
8− * This program is distributed in the hope that it will be useful,
9− * but WITHOUT ANY WARRANTY; without even the implied warranty of
10− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11− * GNU General Public License for more details.
12− *
13− * You should have received a copy of the GNU General Public License
14− * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− *
16− * Author: Michael Zanetti <michael.zanetti@canonical.com>
17− */
18−
19−#include "audio.h"
20−
21−Audio::Audio(QObject* parent):
22− QObject(parent),
23− m_playbackState(StoppedState)
24−{
25− qsrand(time(nullptr));
26− m_timer.setInterval(1000);
27− connect(&m_timer, SIGNAL(timeout()), SLOT(timerEvent()));
28−}
29−
30−QUrl Audio::source() const
31−{
32− return m_source;
33−}
34−
35−void Audio::setSource(const QUrl &source)
36−{
37− if (m_source != source) {
38− m_source = source;
39− Q_EMIT sourceChanged(source);
40−
41− m_position = 0;
42− Q_EMIT positionChanged(m_position);
43−
44− m_duration = (qrand() % 20000) + 10000;
45− Q_EMIT durationChanged(m_duration);
46− }
47−}
48−
49−Audio::PlaybackState Audio::playbackState() const
50−{
51− return m_playbackState;
52−}
53−
54−int Audio::position() const
55−{
56− return m_position;
57−}
58−
59−int Audio::duration() const
60−{
61− return m_duration;
62−}
63−
64−QString Audio::errorString() const
65−{
66− return QString();
67−}
68−
69−void Audio::pause()
70−{
71− if (m_playbackState == PlayingState) {
72− m_playbackState = PausedState;
73− Q_EMIT playbackStateChanged(m_playbackState);
74− m_timer.stop();
75− }
76−}
77−
78−void Audio::play()
79−{
80− if (m_playbackState != PlayingState) {
81− m_playbackState = PlayingState;
82− Q_EMIT playbackStateChanged(m_playbackState);
83−
84− m_timer.start();
85− }
86−}
87−
88−void Audio::stop()
89−{
90− if (m_playbackState != StoppedState) {
91− m_playbackState = StoppedState;
92− Q_EMIT playbackStateChanged(m_playbackState);
93− m_timer.stop();
94− m_position = 0;
95− Q_EMIT positionChanged(m_position);
96− }
97−}
98−
99−void Audio::timerEvent()
100−{
101− if (m_position + 1000 < m_duration) {
102− m_position += 1000;
103− Q_EMIT positionChanged(m_position);
104− } else {
105− stop();
106− }
107−}
108−
109−Audio::AudioRole Audio::audioRole() const
110−{
111− return Audio::MultimediaRole;
112−}
113−
114−void Audio::setAudioRole(Audio::AudioRole audioRole)
115−{
116− Q_UNUSED(audioRole);
117−}
tests/mocks/QtMultimedia/audio.hdeleted−90
@@ -1,90 +0,0 @@
1−/*
2− * Copyright (C) 2012,2013 Canonical, Ltd.
3− *
4− * This program is free software; you can redistribute it and/or modify
5− * it under the terms of the GNU General Public License as published by
6− * the Free Software Foundation; version 3.
7− *
8− * This program is distributed in the hope that it will be useful,
9− * but WITHOUT ANY WARRANTY; without even the implied warranty of
10− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11− * GNU General Public License for more details.
12− *
13− * You should have received a copy of the GNU General Public License
14− * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− *
16− * Authors: Michael Zanetti <michael.zanetti@canonical.com>
17− */
18−
19−#ifndef MOCK_AUDIO_H
20−#define MOCK_AUDIO_H
21−
22−#include <QObject>
23−#include <QUrl>
24−#include <QTimer>
25−
26−class Audio: public QObject
27−{
28− Q_OBJECT
29− Q_ENUMS(PlaybackState)
30− Q_ENUMS(AudioRole)
31− Q_PROPERTY(QUrl source READ source WRITE setSource NOTIFY sourceChanged)
32− Q_PROPERTY(PlaybackState playbackState READ playbackState NOTIFY playbackStateChanged)
33− Q_PROPERTY(int position READ position NOTIFY positionChanged)
34− Q_PROPERTY(int duration READ duration NOTIFY durationChanged)
35− Q_PROPERTY(QString errorString READ errorString NOTIFY errorStringChanged)
36− Q_PROPERTY(AudioRole audioRole READ audioRole WRITE setAudioRole)
37−public:
38− enum PlaybackState {
39− PlayingState,
40− PausedState,
41− StoppedState
42− };
43−
44− enum AudioRole {
45− AlarmRole,
46− AlertRole,
47− MultimediaRole,
48− PhoneRole
49− };
50−
51− explicit Audio(QObject *parent = 0);
52−
53− QUrl source() const;
54− void setSource(const QUrl &source);
55−
56− PlaybackState playbackState() const;
57−
58− int position() const;
59−
60− int duration() const;
61−
62− QString errorString() const;
63−
64− AudioRole audioRole() const;
65− void setAudioRole(AudioRole audioRole);
66−
67−public Q_SLOTS:
68− void pause();
69− void play();
70− void stop();
71−
72−Q_SIGNALS:
73− void sourceChanged(const QUrl &source);
74− void playbackStateChanged(PlaybackState playbackState);
75− void positionChanged(int position);
76− void durationChanged(int duration);
77− void errorStringChanged(const QString &errorString);
78−
79−private Q_SLOTS:
80− void timerEvent();
81−
82−private:
83− QUrl m_source;
84− PlaybackState m_playbackState;
85− QTimer m_timer;
86− int m_position;
87− int m_duration;
88−};
89−
90−#endif
tests/mocks/QtMultimedia/mediaplayer.cppnew file+288
@@ -0,0 +1,288 @@
1+/*
2+ * Copyright (C) 2013 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ *
16+ * Author: Michael Zanetti <michael.zanetti@canonical.com>
17+ */
18+
19+#include "mediaplayer.h"
20+
21+#include <QDebug>
22+#include <QSize>
23+
24+class MetaDataObject: public QObject
25+{
26+ Q_OBJECT
27+ Q_PROPERTY(QVariant title READ title NOTIFY metaDataChanged)
28+ Q_PROPERTY(QVariant resolution READ resolution NOTIFY metaDataChanged)
29+public:
30+ MetaDataObject(MediaPlayer* parent = nullptr)
31+ : QObject(parent)
32+ , m_source(parent)
33+ {
34+ }
35+
36+ QVariant title() const { return getProperty("title"); }
37+ QVariant resolution() const { return getProperty("resolution", QSize(160, 90)); }
38+
39+ QVariant getProperty(const QString& property, QVariant defaultValue = QVariant()) const {
40+ MediaDataSource* sourceData = MediaPlayerDataController::instance()->dataForSource(m_source->source());
41+ QVariant metaData = sourceData->metaData();
42+ return metaData.toMap().value(property, defaultValue);
43+ }
44+
45+Q_SIGNALS:
46+ void metaDataChanged();
47+
48+private:
49+ MediaPlayer* m_source;
50+};
51+
52+MediaPlayer::MediaPlayer(QObject* parent)
53+ : QObject(parent)
54+ , m_playbackState(StoppedState)
55+ , m_status(NoMedia)
56+ , m_metaData(new MetaDataObject(this))
57+{
58+ qsrand(time(nullptr));
59+ m_timer.setInterval(100);
60+ connect(&m_timer, SIGNAL(timeout()), SLOT(timerEvent()));
61+
62+ connect(MediaPlayerDataController::instance(), &MediaPlayerDataController::sourceRemoved,
63+ this, [this](const QUrl&) {
64+ disconnect(0, &MediaDataSource::durationChanged, this, &MediaPlayer::durationChanged);
65+ disconnect(0, &MediaDataSource::seekableChanged, this, &MediaPlayer::seekableChanged);
66+ disconnect(0, &MediaDataSource::availabilityChanged, this, &MediaPlayer::availabilityChanged);
67+ });
68+
69+ connect(MediaPlayerDataController::instance(), &MediaPlayerDataController::sourceAdded,
70+ this, [this](const QUrl& source) {
71+ MediaDataSource* dataSource = MediaPlayerDataController::instance()->dataForSource(source);
72+ if (!dataSource) return;
73+
74+ connect(dataSource, &MediaDataSource::durationChanged, this, &MediaPlayer::durationChanged);
75+ connect(dataSource, &MediaDataSource::seekableChanged,this, &MediaPlayer::seekableChanged);
76+ connect(dataSource, &MediaDataSource::availabilityChanged,this, &MediaPlayer::availabilityChanged);
77+ });
78+}
79+
80+QUrl MediaPlayer::source() const
81+{
82+ return m_source;
83+}
84+
85+void MediaPlayer::setSource(const QUrl &source)
86+{
87+ if (m_source != source) {
88+ m_source = source;
89+ Q_EMIT sourceChanged(source);
90+ Q_EMIT durationChanged(duration());
91+ Q_EMIT seekableChanged(isSeekable());
92+ Q_EMIT availabilityChanged(availability());
93+
94+ m_position = 0;
95+ Q_EMIT positionChanged(m_position);
96+
97+ m_status = availability()==Available ? Loaded : InvalidMedia;
98+ Q_EMIT statusChanged();
99+ }
100+}
101+
102+MediaPlayer::PlaybackState MediaPlayer::playbackState() const
103+{
104+ return m_playbackState;
105+}
106+
107+int MediaPlayer::position() const
108+{
109+ return m_position;
110+}
111+
112+int MediaPlayer::duration() const
113+{
114+ MediaDataSource* dataSource = MediaPlayerDataController::instance()->dataForSource(source());
115+ if (dataSource) return dataSource->duration();
116+ return 0;
117+}
118+
119+QString MediaPlayer::errorString() const
120+{
121+ return QString();
122+}
123+
124+void MediaPlayer::pause()
125+{
126+ if (m_playbackState == PlayingState) {
127+ m_playbackState = PausedState;
128+ Q_EMIT playbackStateChanged(m_playbackState);
129+ m_timer.stop();
130+ }
131+}
132+
133+void MediaPlayer::play()
134+{
135+ if (m_playbackState != PlayingState) {
136+ m_playbackState = PlayingState;
137+ Q_EMIT playbackStateChanged(m_playbackState);
138+
139+ m_timer.start();
140+ }
141+}
142+
143+void MediaPlayer::stop()
144+{
145+ if (m_playbackState != StoppedState) {
146+ m_playbackState = StoppedState;
147+ Q_EMIT playbackStateChanged(m_playbackState);
148+ m_timer.stop();
149+ m_position = 0;
150+ Q_EMIT positionChanged(m_position);
151+ }
152+}
153+
154+void MediaPlayer::seek(int position)
155+{
156+ if (status() != Loaded) return;
157+ int newPosition = qMin(qMax(0, position), duration());
158+ if (newPosition != m_position) {
159+ m_position = newPosition;
160+ Q_EMIT positionChanged(m_position);
161+ }
162+
163+}
164+
165+void MediaPlayer::timerEvent()
166+{
167+ if (m_position + m_timer.interval() < duration()) {
168+ m_position += m_timer.interval();
169+ Q_EMIT positionChanged(m_position);
170+ } else {
171+ pause();
172+ }
173+}
174+
175+MediaPlayer::AudioRole MediaPlayer::audioRole() const
176+{
177+ return MediaPlayer::MultimediaRole;
178+}
179+
180+void MediaPlayer::setAudioRole(MediaPlayer::AudioRole audioRole)
181+{
182+ Q_UNUSED(audioRole);
183+}
184+
185+bool MediaPlayer::isSeekable() const
186+{
187+ MediaDataSource* dataSource = MediaPlayerDataController::instance()->dataForSource(source());
188+ if (dataSource) return dataSource->isSeekable();
189+ return true;
190+}
191+
192+MediaPlayer::Availability MediaPlayer::availability() const
193+{
194+ MediaDataSource* dataSource = MediaPlayerDataController::instance()->dataForSource(source());
195+ if (dataSource) return dataSource->availability();
196+ return Available;
197+}
198+
199+
200+MediaDataSource::MediaDataSource(QObject *parent)
201+ : QObject(parent)
202+ , m_seekable(true)
203+ , m_duration((qrand() % 20000) + 10000)
204+ , m_availability(MediaPlayer::Available)
205+{
206+}
207+
208+MediaDataSource::~MediaDataSource()
209+{
210+ if (!m_source.isEmpty()) MediaPlayerDataController::instance()->unregisterDataSource(this);
211+}
212+
213+void MediaDataSource::setSource(const QUrl &source)
214+{
215+ if (m_source != source) {
216+ if (!m_source.isEmpty()) MediaPlayerDataController::instance()->unregisterDataSource(this);
217+
218+ m_source = source;
219+
220+ if (!m_source.isEmpty()) MediaPlayerDataController::instance()->registerDataSource(this);
221+ Q_EMIT sourceChanged();
222+ }
223+}
224+
225+void MediaDataSource::setSeekable(bool seekable)
226+{
227+ if (m_seekable != seekable) {
228+ m_seekable = seekable;
229+ Q_EMIT seekableChanged(m_seekable);
230+ }
231+}
232+
233+void MediaDataSource::setDuration(int duration)
234+{
235+ if (m_duration != duration) {
236+ m_duration = duration;
237+ Q_EMIT durationChanged(m_duration);
238+ }
239+}
240+
241+void MediaDataSource::setAvailability(MediaPlayer::Availability availability)
242+{
243+ if (m_availability != availability) {
244+ m_availability = availability;
245+ Q_EMIT availabilityChanged(m_availability);
246+ }
247+}
248+
249+void MediaDataSource::setMetaData(const QVariant& metaData)
250+{
251+ if (m_metaData != metaData) {
252+ m_metaData = metaData;
253+ Q_EMIT metaDataChanged();
254+ }
255+}
256+
257+MediaPlayerDataController *MediaPlayerDataController::instance()
258+{
259+ static MediaPlayerDataController* instance = nullptr;
260+ if (!instance) { instance = new MediaPlayerDataController(); }
261+ return instance;
262+}
263+
264+void MediaPlayerDataController::registerDataSource(MediaDataSource *dataSource)
265+{
266+ m_dataSources[dataSource->source()] = dataSource;
267+ Q_EMIT sourceAdded(dataSource->source());
268+}
269+
270+void MediaPlayerDataController::unregisterDataSource(MediaDataSource *dataSource)
271+{
272+ QList<QUrl> keys = m_dataSources.keys(dataSource);
273+ Q_FOREACH(const QUrl& key, keys) {
274+ m_dataSources.remove(key);
275+ Q_EMIT sourceRemoved(key);
276+ }
277+}
278+
279+MediaDataSource *MediaPlayerDataController::dataForSource(const QUrl &source)
280+{
281+ if (!m_dataSources.contains(source)) {
282+ static MediaDataSource defaultSource;
283+ return &defaultSource;
284+ }
285+ return m_dataSources[source];
286+}
287+
288+#include "mediaplayer.moc"
tests/mocks/QtMultimedia/mediaplayer.hnew file+195
@@ -0,0 +1,195 @@
1+/*
2+ * Copyright (C) 2012,2013 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ *
16+ * Authors: Michael Zanetti <michael.zanetti@canonical.com>
17+ */
18+
19+#ifndef MOCK_MEDIAPLAYER_H
20+#define MOCK_MEDIAPLAYER_H
21+
22+#include <QObject>
23+#include <QUrl>
24+#include <QTimer>
25+#include <QHash>
26+#include <QVariant>
27+
28+class MediaPlayer: public QObject
29+{
30+ Q_OBJECT
31+
32+ Q_PROPERTY(QUrl source READ source WRITE setSource NOTIFY sourceChanged)
33+ Q_PROPERTY(PlaybackState playbackState READ playbackState NOTIFY playbackStateChanged)
34+ Q_PROPERTY(int position READ position NOTIFY positionChanged)
35+ Q_PROPERTY(int duration READ duration NOTIFY durationChanged)
36+ Q_PROPERTY(QString errorString READ errorString NOTIFY errorStringChanged)
37+ Q_PROPERTY(AudioRole audioRole READ audioRole WRITE setAudioRole)
38+
39+ Q_PROPERTY(bool seekable READ isSeekable NOTIFY seekableChanged)
40+ Q_PROPERTY(Availability availability READ availability NOTIFY availabilityChanged)
41+ Q_PROPERTY(Status status READ status NOTIFY statusChanged)
42+ Q_PROPERTY(QObject *metaData READ metaData CONSTANT)
43+
44+ Q_ENUMS(PlaybackState)
45+ Q_ENUMS(AudioRole)
46+ Q_ENUMS(Availability)
47+ Q_ENUMS(Status)
48+public:
49+ enum PlaybackState {
50+ PlayingState,
51+ PausedState,
52+ StoppedState
53+ };
54+
55+ enum AudioRole {
56+ AlarmRole,
57+ AlertRole,
58+ MultimediaRole,
59+ PhoneRole
60+ };
61+
62+ enum Availability {
63+ Available,
64+ Busy,
65+ Unavailable,
66+ ResourceMissing
67+ };
68+
69+ enum Status
70+ {
71+ UnknownStatus,
72+ NoMedia,
73+ Loading,
74+ Loaded,
75+ Stalled,
76+ Buffering,
77+ Buffered,
78+ EndOfMedia,
79+ InvalidMedia
80+ };
81+
82+ explicit MediaPlayer(QObject *parent = 0);
83+
84+ QUrl source() const;
85+ void setSource(const QUrl &source);
86+
87+ PlaybackState playbackState() const;
88+
89+ int position() const;
90+
91+ int duration() const;
92+
93+ QString errorString() const;
94+
95+ AudioRole audioRole() const;
96+ void setAudioRole(AudioRole audioRole);
97+
98+ bool isSeekable() const;
99+ MediaPlayer::Availability availability() const;
100+ Status status() const { return m_status; }
101+ QObject *metaData() const { return m_metaData; }
102+
103+public Q_SLOTS:
104+ void pause();
105+ void play();
106+ void stop();
107+ void seek(int position);
108+
109+Q_SIGNALS:
110+ void sourceChanged(const QUrl &source);
111+ void playbackStateChanged(PlaybackState playbackState);
112+ void positionChanged(int position);
113+ void durationChanged(int duration);
114+ void seekableChanged(bool seekable);
115+ void errorStringChanged(const QString &errorString);
116+ void availabilityChanged(Availability availability);
117+ void statusChanged();
118+
119+private Q_SLOTS:
120+ void timerEvent();
121+
122+private:
123+ QUrl m_source;
124+ PlaybackState m_playbackState;
125+ QTimer m_timer;
126+ int m_position;
127+ Status m_status;
128+ QObject *m_metaData;
129+};
130+
131+class MediaDataSource : public QObject
132+{
133+ Q_OBJECT
134+ Q_PROPERTY(QUrl source READ source WRITE setSource NOTIFY sourceChanged)
135+ Q_PROPERTY(bool seekable READ isSeekable WRITE setSeekable NOTIFY seekableChanged)
136+ Q_PROPERTY(int duration READ duration WRITE setDuration NOTIFY durationChanged)
137+ Q_PROPERTY(MediaPlayer::Availability availability READ availability WRITE setAvailability NOTIFY availabilityChanged)
138+ Q_PROPERTY(QVariant metaData READ metaData WRITE setMetaData NOTIFY metaDataChanged)
139+
140+public:
141+ MediaDataSource(QObject* parent = 0);
142+ ~MediaDataSource();
143+
144+ QUrl source() const { return m_source; }
145+ void setSource(const QUrl& source);
146+
147+ bool isSeekable() const { return m_seekable; }
148+ void setSeekable(bool seekable);
149+
150+ int duration() const { return m_duration; }
151+ void setDuration(int duration);
152+
153+ MediaPlayer::Availability availability() const { return m_availability; }
154+ void setAvailability(MediaPlayer::Availability availability);
155+
156+ QVariant metaData() const { return m_metaData; }
157+ void setMetaData(const QVariant& metaData);
158+
159+Q_SIGNALS:
160+ void sourceChanged();
161+ void seekableChanged(bool seekable);
162+ void durationChanged(int duration);
163+ void availabilityChanged(MediaPlayer::Availability availability);
164+ void metaDataChanged();
165+
166+private:
167+ QUrl m_source;
168+ bool m_seekable;
169+ int m_duration;
170+ MediaPlayer::Availability m_availability;
171+ QVariant m_metaData;
172+};
173+
174+class MediaPlayerDataController : public QObject
175+{
176+ Q_OBJECT
177+public:
178+ MediaPlayerDataController() = default;
179+
180+ static MediaPlayerDataController *instance();
181+
182+ void registerDataSource(MediaDataSource* dataSource);
183+ void unregisterDataSource(MediaDataSource* dataSource);
184+
185+ MediaDataSource* dataForSource(const QUrl& source);
186+
187+Q_SIGNALS:
188+ void sourceAdded(const QUrl& source);
189+ void sourceRemoved(const QUrl& source);
190+
191+public:
192+ QHash<QUrl, MediaDataSource*> m_dataSources;
193+};
194+
195+#endif // MOCK_MEDIAPLAYER_H
tests/mocks/QtMultimedia/plugin.cpp+7−2
@@ -17,12 +17,17 @@
1717 */
1818
1919#include "plugin.h"
20−#include "audio.h"
20+#include "mediaplayer.h"
21+#include "videooutput.h"
2122
2223#include <QtQml/qqml.h>
2324
2425void MockQtMultimediaPlugin::registerTypes(const char *uri)
2526{
2627 Q_ASSERT(uri == QLatin1String("QtMultimedia"));
27− qmlRegisterType<Audio>(uri, 5, 0, "Audio");
28+ qmlRegisterType<MediaPlayer>(uri, 5, 0, "Audio");
29+ qmlRegisterType<MediaPlayer>(uri, 5, 0, "MediaPlayer");
30+ qmlRegisterType<VideoOutput>(uri, 5, 0, "VideoOutput");
31+
32+ qmlRegisterType<MediaDataSource>(uri, 5, 0, "MediaDataSource");
2833}
tests/mocks/QtMultimedia/videooutput.cppnew file+111
@@ -0,0 +1,111 @@
1+#include "videooutput.h"
2+#include "mediaplayer.h"
3+
4+#include <paths.h>
5+
6+#include <QQuickView>
7+#include <QQmlComponent>
8+#include <QGuiApplication>
9+#include <QQmlProperty>
10+#include <QQmlEngine>
11+
12+VideoOutput::VideoOutput(QQuickItem *parent)
13+ : QQuickItem(parent)
14+ , m_source(nullptr)
15+ , m_qmlContentComponent(nullptr)
16+ , m_qmlItem(nullptr)
17+{
18+}
19+
20+void VideoOutput::setSource(QObject *source)
21+{
22+ if (m_source != source) {
23+ if (m_source) disconnect(m_source, 0, this, 0);
24+
25+ m_source = source;
26+ Q_EMIT sourceChanged();
27+
28+ MediaPlayer* mediaPlayer = qobject_cast<MediaPlayer*>(source);
29+ if (mediaPlayer) {
30+ connect(mediaPlayer, &MediaPlayer::positionChanged, this, &VideoOutput::updateProperties);
31+ connect(mediaPlayer, &MediaPlayer::playbackStateChanged, this, &VideoOutput::updateProperties);
32+ connect(mediaPlayer->metaData(), SIGNAL(metaDataChanged()), this, SLOT(updateProperties()));
33+ }
34+ }
35+}
36+
37+void VideoOutput::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value)
38+{
39+ if (change == QQuickItem::ItemSceneChange && !m_qmlContentComponent) {
40+ QWindowList list = QGuiApplication::topLevelWindows();
41+ if (list.isEmpty()) return;
42+
43+ // The assumptions I make here really should hold.
44+ QQuickView *quickView =
45+ qobject_cast<QQuickView*>(list[0]);
46+
47+ m_qmlContentComponent = new QQmlComponent(quickView->engine(),
48+ QString("%1/QtMultimedia/VideoSurface.qml").arg(mockPluginsDir()));
49+
50+ switch (m_qmlContentComponent->status()) {
51+ case QQmlComponent::Ready:
52+ createQmlContentItem();
53+ break;
54+ case QQmlComponent::Loading:
55+ connect(m_qmlContentComponent, &QQmlComponent::statusChanged,
56+ this, &VideoOutput::onComponentStatusChanged);
57+ break;
58+ case QQmlComponent::Error:
59+ printComponentErrors();
60+ qFatal("VideoOutput: failed to create content component.");
61+ break;
62+ default:
63+ qFatal("VideoOutput: Unhandled component status");
64+ }
65+ }
66+ QQuickItem::itemChange(change, value);
67+}
68+
69+void VideoOutput::printComponentErrors()
70+{
71+ QList<QQmlError> errors = m_qmlContentComponent->errors();
72+ for (int i = 0; i < errors.count(); ++i) {
73+ qDebug() << errors[i];
74+ }
75+}
76+
77+void VideoOutput::onComponentStatusChanged(QQmlComponent::Status status)
78+{
79+ if (status == QQmlComponent::Ready) {
80+ createQmlContentItem();
81+ }
82+}
83+
84+void VideoOutput::createQmlContentItem()
85+{
86+ m_qmlItem = qobject_cast<QQuickItem*>(m_qmlContentComponent->create());
87+ m_qmlItem->setParentItem(this);
88+
89+ updateProperties();
90+}
91+
92+void VideoOutput:: updateProperties()
93+{
94+ if (!m_qmlItem) return;
95+
96+ MediaPlayer* mediaPlayer = qobject_cast<MediaPlayer*>(m_source);
97+ if (!mediaPlayer) return;
98+
99+ QQmlProperty playbackStateProperty(m_qmlItem, "playbackState");
100+ playbackStateProperty.write(QVariant::fromValue<int>(mediaPlayer->playbackState()));
101+
102+ QQmlProperty positionProperty(m_qmlItem, "position");
103+ positionProperty.write(QVariant::fromValue(mediaPlayer->position()));
104+
105+ QQmlProperty propResSource(mediaPlayer->metaData(), "resolution");
106+ QQmlProperty propResDest(m_qmlItem, "resolution");
107+
108+ if (propResSource.isValid() && propResDest.isValid()) {
109+ propResDest.write(propResSource.read());
110+ }
111+}
tests/mocks/QtMultimedia/videooutput.hnew file+36
@@ -0,0 +1,36 @@
1+#ifndef VIDEOOUTPUT_H
2+#define VIDEOOUTPUT_H
3+
4+#include <QQuickItem>
5+#include <QPointer>
6+class QQmlComponent;
7+
8+class VideoOutput : public QQuickItem
9+{
10+ Q_OBJECT
11+ Q_PROPERTY(QObject* source READ source WRITE setSource NOTIFY sourceChanged)
12+public:
13+ explicit VideoOutput(QQuickItem *parent = 0);
14+
15+ QObject *source() const { return m_source.data(); }
16+ void setSource(QObject *source);
17+
18+ void itemChange(ItemChange change, const ItemChangeData & value);
19+
20+Q_SIGNALS:
21+ void sourceChanged();
22+
23+protected Q_SLOTS:
24+ void onComponentStatusChanged(QQmlComponent::Status status);
25+ void updateProperties();
26+
27+private:
28+ void createQmlContentItem();
29+ void printComponentErrors();
30+
31+ QPointer<QObject> m_source;
32+ QQmlComponent* m_qmlContentComponent;
33+ QQuickItem* m_qmlItem;
34+};
35+
36+#endif // VIDEOOUTPUT_H
tests/qmltests/CMakeLists.txt+2−1
@@ -32,6 +32,7 @@ add_qml_test(Components ResponsiveGridView)
3232add_qml_test(Components ResponsiveVerticalJournal IMPORT_PATHS ${qmltest_DEFAULT_IMPORT_PATHS} ${CMAKE_BINARY_DIR}/plugins)
3333add_qml_test(Components Showable)
3434add_qml_test(Components ZoomableImage)
35+add_qml_test(Components MediaServices IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins ${CMAKE_BINARY_DIR}/tests/utils/modules)
3536add_qml_test(Dash Dash)
3637add_qml_test(Dash DashContent)
3738add_qml_test(Dash Card)
@@ -46,7 +47,7 @@ add_qml_test(Dash/Previews PreviewAudioPlayback)
4647add_qml_test(Dash/Previews PreviewExpandable)
4748add_qml_test(Dash/Previews PreviewHeader)
4849add_qml_test(Dash/Previews PreviewImageGallery)
49−add_qml_test(Dash/Previews PreviewInlineVideo IMPORT_PATHS ${CMAKE_BINARY_DIR}/tests/utils/modules ${CMAKE_BINARY_DIR}/plugins)
50+add_qml_test(Dash/Previews PreviewInlineVideo)
5051add_qml_test(Dash/Previews PreviewPayments)
5152add_qml_test(Dash/Previews PreviewProgress)
5253add_qml_test(Dash/Previews PreviewRatingDisplay)
tests/qmltests/Components/tst_MediaServices.qmlnew file+204
@@ -0,0 +1,204 @@
1+/*
2+ * Copyright 2015 Canonical Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.0
18+import QtTest 1.0
19+import "../../../qml/Components/MediaServices"
20+import Unity.Test 0.1 as UT
21+import Ubuntu.Components 1.3
22+import QtMultimedia 5.0
23+
24+Rectangle {
25+ id: root
26+ width: units.gu(70)
27+ height: units.gu(80)
28+ color: "lightgrey"
29+
30+ property var sourceData: {
31+ "source": "file:///home/nick/Videos/test-mpeg.ogv",
32+ "screenshot": "/home/nick/Pictures/malta.jpg"
33+ }
34+
35+// MediaDataSource {
36+// source: root.sourceData["source"]
37+// duration: 60000
38+// metaData: {
39+// "title" : "TEST MPEG",
40+// "resolution" : { "width": 160, "height": 90 }
41+// }
42+// }
43+
44+ Item {
45+ anchors.fill: parent
46+
47+ Rectangle {
48+ anchors {
49+ top: parent.top
50+ bottom: parent.bottom
51+ left: parent.left
52+ right: controls.left
53+ }
54+ color: "darkblue"
55+
56+ MediaServices {
57+ id: services
58+ fullscreen: false
59+ width: parent.width
60+ sourceData: root.sourceData
61+ context: "video"
62+
63+ rootItem: parent
64+ }
65+ }
66+
67+ Rectangle {
68+ id: controls
69+ color: "darkgrey"
70+ anchors {
71+ top: parent.top
72+ bottom: parent.bottom
73+ right: parent.right
74+ }
75+ width: units.gu(30)
76+
77+ Column {
78+ anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
79+
80+ Button {
81+ text: "Fullscreen"
82+ onClicked: services.fullscreen = !services.fullscreen
83+ }
84+ UT.MouseTouchEmulationCheckbox {}
85+ }
86+ }
87+ }
88+
89+ SignalSpy {
90+ id: serviceCloseSpy
91+ target: services
92+ signalName: "close"
93+ }
94+
95+ UT.UnityTestCase {
96+ name: "VideoMediaServices"
97+ when: windowShown
98+
99+ property int oldControlTimerInterval: 0;
100+
101+ function init() {
102+ services.context = "video";
103+
104+ var controlHideTimer = findInvisibleChild(services, "controlHideTimer");
105+ verify(controlHideTimer !== null);
106+ oldControlTimerInterval = controlHideTimer.interval;
107+ }
108+
109+ function cleanup() {
110+ serviceCloseSpy.clear();
111+ services.context = "";
112+ services.fullscreen = false;
113+ tryCompareFunction(function() { return services.header ? services.header.visible : false }, false);
114+ tryCompareFunction(function() { return services.footer ? services.footer.visible : false }, false);
115+
116+ var controlHideTimer = findInvisibleChild(services, "controlHideTimer");
117+ controlHideTimer.interval = oldControlTimerInterval;
118+ }
119+
120+ function test_videoWidget() {
121+ var videoPlayer = findChild(services, "videoPlayer");
122+ verify(videoPlayer !== null);
123+
124+ var videoControls = findChild(services, "videoControls");
125+ verify(videoControls !== null);
126+ }
127+
128+ function test_tapVideoPlayerToPlayAndPause() {
129+ var videoPlayer = findChild(services, "videoPlayer");
130+ verify(videoPlayer !== null)
131+ tap(videoPlayer, videoPlayer.width/2, videoPlayer.height/2);
132+
133+ var mediaPlayer = findInvisibleChild(services, "mediaPlayer");
134+ verify(mediaPlayer !== null);
135+ compare(mediaPlayer.playbackState, MediaPlayer.PlayingState, "Media player should be playing");
136+
137+ tap(videoPlayer, videoPlayer.width/2, videoPlayer.height/2);
138+ compare(mediaPlayer.playbackState, MediaPlayer.PausedState, "Media player should be playing");
139+ }
140+
141+ function test_fullscreenSwitch() {
142+ var mediaPlayer = findInvisibleChild(services, "mediaPlayer");
143+ verify(mediaPlayer !== null);
144+ mediaPlayer.play();
145+ wait(UbuntuAnimation.BriskDuration); // animation
146+
147+ var button = findChild(services, "viewActionButton");
148+ verify(button !== null);
149+ tap(button);
150+
151+ compare(services.fullscreen, true, "Should have switched to fullscreen mode.");
152+ }
153+
154+ function test_HeaderVisibleOnlyWhenFullscreen() {
155+ services.fullscreen = false;
156+ compare(services.header, null, "Header should be null when not fullscreen");
157+ services.fullscreen = true;
158+ tryCompareFunction(function() { return services.header !== null }, true);
159+ }
160+
161+ function test_ControlsShowAndHideWhenPlayed() {
162+ services.fullscreen = true;
163+ var controlHideTimer = findInvisibleChild(services, "controlHideTimer");
164+ controlHideTimer.interval = 100;
165+
166+ var mediaPlayer = findInvisibleChild(services, "mediaPlayer");
167+ mediaPlayer.play();
168+
169+ tryCompare(services.header, "visible", true);
170+ tryCompare(services.footer, "visible", true);
171+
172+ tryCompare(services.header, "visible", false);
173+ tryCompare(services.footer, "visible", false);
174+ }
175+
176+ function test_ControlsDontTimeOutWhenPaused() {
177+ services.fullscreen = true;
178+ var controlHideTimer = findInvisibleChild(services, "controlHideTimer");
179+ controlHideTimer.interval = 100;
180+
181+ var mediaPlayer = findInvisibleChild(services, "mediaPlayer");
182+ mediaPlayer.play();
183+ mediaPlayer.pause();
184+ wait(300);
185+
186+ compare(services.header.visible, true, "Header should still be visible");
187+ compare(services.footer.visible, true, "Footer should still be visible");
188+ }
189+
190+ function test_close() {
191+ services.fullscreen = true;
192+ var mediaPlayer = findInvisibleChild(services, "mediaPlayer");
193+ mediaPlayer.play();
194+ mediaPlayer.pause();
195+ wait(UbuntuAnimation.BriskDuration); // animation
196+
197+ var navigationButton = findChild(services, "navigationButton");
198+ verify(navigationButton !== null);
199+
200+ tap(navigationButton);
201+ compare(serviceCloseSpy.count, 1, "close was not called");
202+ }
203+ }
204+}
tests/qmltests/Dash/Previews/tst_PreviewInlineVideo.qml+50−18
@@ -1,5 +1,5 @@
11/*
2− * Copyright 2014 Canonical Ltd.
2+ * Copyright 2015 Canonical Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -19,36 +19,67 @@ import QtTest 1.0
1919import "../../../../qml/Dash/Previews"
2020import Unity.Test 0.1 as UT
2121import Ubuntu.Components 1.3
22+import QtMultimedia 5.0
2223
2324Rectangle {
24− id: mainView
25− width: units.gu(40)
25+ width: units.gu(70)
2626 height: units.gu(80)
2727 color: "lightgrey"
2828
2929 property var widgetData0: {
30− "source": "video:///home/nick/Videos/test-mpeg.ogv",
31− "screenshot": "/home/nick/Pictures/malta.jpg"
32−// "screenshot": "image://thumbnailer/https://dl.dropboxusercontent.com/u/85539674/team11.ogg"
30+ "source": "file:///test-video1",
31+ "screenshot": "file:///home/nick/Pictures/IMG_20150513_132759.jpg"
3332 }
3433
3534 property var widgetData1: {
36−// "source": "https://dl.dropboxusercontent.com/u/85539674/test-mpeg.ogv",
37−// "screenshot": "/home/nick/Pictures/malta.jpg"
35+ "source": "file:///test-video2",
36+ "screenshot": "file:///test-video2-screenshot"
3837 }
3938
40− function pushPage(page) { pageStack.push(page); }
41− function popPage(page) { pageStack.pop(); }
39+ MediaDataSource {
40+ source: "file:///home/nick/Videos/test-mpeg.ogv"
41+ duration: 60000
42+ metaData: {
43+ "title" : "TEST MPEG",
44+ "resolution" : { "width": 100, "height": 150 }
45+ }
46+ }
4247
43− PageStack {
44− id: pageStack
48+ Item {
4549 anchors.fill: parent
46− anchors.margins: units.gu(5)
4750
48− PreviewInlineVideo {
49− id: videoPlayback
50− width: parent.width
51− widgetData: widgetData0
51+ Rectangle {
52+ anchors {
53+ top: parent.top
54+ bottom: parent.bottom
55+ left: parent.left
56+ right: controls.left
57+ }
58+
59+ PreviewInlineVideo {
60+ id: videoPlayback
61+ width: parent.width
62+ widgetData: widgetData0
63+
64+ rootItem: parent
65+ }
66+ }
67+
68+ Rectangle {
69+ id: controls
70+ color: "darkgrey"
71+ anchors {
72+ top: parent.top
73+ bottom: parent.bottom
74+ right: parent.right
75+ }
76+ width: units.gu(30)
77+
78+ Column {
79+ anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
80+
81+ UT.MouseTouchEmulationCheckbox {}
82+ }
5283 }
5384 }
5485
@@ -58,10 +89,11 @@ Rectangle {
5889
5990 function test_loadScreenshot() {
6091 var screenshot = findChild(videoPlayback, "screenshot");
92+ verify(screenshot !== null);
6193
6294 videoPlayback.widgetData = widgetData1;
6395 var screenshotSource = screenshot.source
64− verify(screenshotSource.toString().indexOf("image://thumbnailer/") === 0)
96+ compare(screenshotSource.toString(), "file:///test-video2-screenshot");
6597 }
6698 }
6799}