Merge lp:~nick-dedekind/unity8/inline-dash-videos into lp:unity8

Proposed by Nick Dedekind
Status: Superseded
Proposed branch: lp:~nick-dedekind/unity8/inline-dash-videos
Merge into: lp:unity8
Diff against target: 2432 lines (+2089/-59)
20 files modified
qml/Components/LazyImage.qml (+2/-0)
qml/Components/MediaServices/MediaServices.qml (+391/-0)
qml/Components/MediaServices/MediaServicesControls.qml (+189/-0)
qml/Components/MediaServices/MediaServicesHeader.qml (+72/-0)
qml/Components/MediaServices/VideoPlayer.qml (+152/-0)
qml/Components/MediaServices/VideoPlayerControls.qml (+182/-0)
qml/Dash/GenericScopeView.qml (+1/-0)
qml/Dash/Previews/PreviewInlineVideo.qml (+89/-0)
qml/Dash/Previews/PreviewWidgetFactory.qml (+8/-1)
tests/mocks/QtMultimedia/CMakeLists.txt (+8/-2)
tests/mocks/QtMultimedia/VideoSurface.qml (+111/-0)
tests/mocks/QtMultimedia/mediaplayer.cpp (+216/-29)
tests/mocks/QtMultimedia/mediaplayer.h (+131/-10)
tests/mocks/QtMultimedia/plugin.cpp (+7/-3)
tests/mocks/QtMultimedia/videooutput.cpp (+127/-0)
tests/mocks/QtMultimedia/videooutput.h (+52/-0)
tests/qmltests/CMakeLists.txt (+2/-0)
tests/qmltests/Components/tst_MediaServices.qml (+228/-0)
tests/qmltests/Dash/Previews/tst_PreviewInlineVideo.qml (+106/-0)
tests/qmltests/Dash/Previews/tst_PreviewWidgetFactory.qml (+15/-14)
To merge this branch: bzr merge lp:~nick-dedekind/unity8/inline-dash-videos
Reviewer Review Type Date Requested Status
Michał Sawicz Needs Fixing
Albert Astals Cid (community) Needs Fixing
PS Jenkins bot (community) continuous-integration Needs Fixing
Jim Hodapp (community) Needs Fixing
Review via email: mp+260251@code.launchpad.net

This proposal has been superseded by a proposal from 2015-12-15.

Commit message

Added live video playback in dash previews

Description of the change

Added live video playback in dash previews.
https://docs.google.com/document/d/1iftJ0_Ypw4Hv2n3LO8kiOM7WSfAlQlImHHEPPPIlu_U/edit#heading=h.lo6wjg7251og

 * Are there any related MPs required for this MP to build/function as expected? Please list.
N/A

 * Did you perform an exploratory manual test run of your code change and any related functionality?
Yes

 * Did you make sure that your branch does not contain spurious tags?
Yes

 * If you changed the packaging (debian), did you subscribe the ubuntu-unity team to this MP?
N/A

 * If you changed the UI, has there been a design review?
Not yet.

 * Did you have a look at the warnings when running tests? Can they be reduced?
N/A

To post a comment you must log in.
Revision history for this message
Nick Dedekind (nick-dedekind) wrote :

Commented out sections in VideoPlayer is waiting on a design review (may need the ShaderEffect), and the in PreviewInlineVideo.qml is for sharing via content hub.

Will be removed before merging.

1781. By Nick Dedekind

removed QtMultimedia req

1782. By Nick Dedekind

reverted req removal

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Albert Astals Cid (aacid) wrote :

Missing copyright headers on the new files

review: Needs Fixing
1783. By Nick Dedekind

re-remove qtmultimedia req!

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
1784. By Nick Dedekind

update components version

1785. By Nick Dedekind

added copywrite. removed unused code. error handling

Revision history for this message
Nick Dedekind (nick-dedekind) wrote :

> Missing copyright headers on the new files

added

1786. By Nick Dedekind

more headers

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Jim Hodapp (jhodapp) wrote :

Need to clarify with the design team about what the user experience should be when a video is larger than the size of the screen. For example, record a video using the camera-app and then preview the video in this new scope code. You'll notice it doesn't fit on the screen and is a pretty poor UX. I don't think the default behavior is desirable but should the VideoOutput size be scaled down to fit or what?

review: Needs Fixing
Revision history for this message
Michael Zanetti (mzanetti) wrote :

> Need to clarify with the design team about what the user experience should be
> when a video is larger than the size of the screen. For example, record a
> video using the camera-app and then preview the video in this new scope code.
> You'll notice it doesn't fit on the screen and is a pretty poor UX. I don't
> think the default behavior is desirable but should the VideoOutput size be
> scaled down to fit or what?

PreserveAspectFit, can't see any other reasonable option.

Revision history for this message
Jim Hodapp (jhodapp) wrote :

Why wouldn't stretch while shrinking the size of the VideoOutput to shrink it be an option? As long as we shrink it preserving the aspect ratio, it should still look just fine since you're not stretching it larger.

Revision history for this message
Nick Dedekind (nick-dedekind) wrote :

> Why wouldn't stretch while shrinking the size of the VideoOutput to shrink it
> be an option? As long as we shrink it preserving the aspect ratio, it should
> still look just fine since you're not stretching it larger.

Not quite sure what you mean here. What are we stretching?
We would scale the video output down to the maximum available space while keeping aspect.

Revision history for this message
Jim Hodapp (jhodapp) wrote :

> > Why wouldn't stretch while shrinking the size of the VideoOutput to shrink
> it
> > be an option? As long as we shrink it preserving the aspect ratio, it should
> > still look just fine since you're not stretching it larger.
>
> Not quite sure what you mean here. What are we stretching?
> We would scale the video output down to the maximum available space while
> keeping aspect.

That's precisely what I mean. Apparently it was a bad word day for me. ;)

1787. By Nick Dedekind

merged trunk

1788. By Nick Dedekind

better handling of sizing

1789. By Nick Dedekind

better lazy image scaling

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Albert Astals Cid (aacid) wrote :

You have bad tags

review: Needs Fixing
Revision history for this message
Albert Astals Cid (aacid) wrote :

Text conflict in tests/qmltests/CMakeLists.txt
1 conflicts encountered.

1790. By Nick Dedekind

merged with trunk

Revision history for this message
Nick Dedekind (nick-dedekind) wrote :

> You have bad tags

fixed and merged.

Revision history for this message
Albert Astals Cid (aacid) :
review: Abstain
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
1791. By Nick Dedekind

reverted to sdk 1.2

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Albert Astals Cid (aacid) wrote :

Should we remove PreviewVideoPlayback.qml since it's unused?

review: Needs Information
Revision history for this message
Albert Astals Cid (aacid) wrote :

There's a tst_MediaServices.qml but the test in the cmake file is not there so i can't run
make testMediaServices.

review: Needs Fixing
Revision history for this message
Albert Astals Cid (aacid) wrote :

When "fullscreen" one can see the "preview page" below (see Video in http://i.imgur.com/MU2VJeo.png ) is that what design wants? Feels a bit weird to me.

review: Needs Information
Revision history for this message
Albert Astals Cid (aacid) wrote :

Text conflict in tests/mocks/QtMultimedia/mediaplayer.cpp
Text conflict in tests/mocks/QtMultimedia/plugin.cpp
2 conflicts encountered.

Revision history for this message
Albert Astals Cid (aacid) wrote :

Change the TRANSLATORS to Translators it's what we settled on because previously we were using both and gettext only supports one.

Also run make pot_file to have an updated .pot

review: Needs Fixing
Revision history for this message
Nick Dedekind (nick-dedekind) wrote :

> When "fullscreen" one can see the "preview page" below (see Video in
> http://i.imgur.com/MU2VJeo.png ) is that what design wants? Feels a bit weird
> to me.

I followed the designs and colors specified in https://docs.google.com/document/d/1iftJ0_Ypw4Hv2n3LO8kiOM7WSfAlQlImHHEPPPIlu_U/edit#

So I guess the answer is yes, until we're told otherwise anyway.

1792. By Nick Dedekind

merged with trunk

1793. By Nick Dedekind

moved inline video to old files

1794. By Nick Dedekind

updated translations

Revision history for this message
Nick Dedekind (nick-dedekind) wrote :

Fixed comments.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Albert Astals Cid (aacid) wrote :

The preview looks weird before the thumbnail arrives

http://i.imgur.com/Ppu8J9R.png

Maybe we need some max in the height that also takes into account the play button?

review: Needs Fixing
1795. By Nick Dedekind

reverted file changes

1796. By Nick Dedekind

use both inline and old video widget

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Nick Dedekind (nick-dedekind) wrote :

> The preview looks weird before the thumbnail arrives
>
> http://i.imgur.com/Ppu8J9R.png
>
> Maybe we need some max in the height that also takes into account the play
> button?

Should be fixed now. Initial screenshot height is playButton height + gu(2) margin.

Revision history for this message
Albert Astals Cid (aacid) wrote :

going back and forth from to a preview of a video that i recorded with the phone ends up in crashes in media-hub and that freezes the dash.

Can we make the media-hub crashing to freeze the dash? Are we waiting for something in a sync manner that should be async?

review: Needs Fixing
Revision history for this message
Jim Hodapp (jhodapp) wrote :

Albert: Can you get a back trace of all of the threads plus a threads info
listing and the media-hub.log for the crash?

On Tue, Jul 14, 2015 at 7:23 AM, Albert Astals Cid <
<email address hidden>> wrote:

> Review: Needs Fixing
>
> going back and forth from to a preview of a video that i recorded with the
> phone ends up in crashes in media-hub and that freezes the dash.
>
> Can we make the media-hub crashing to freeze the dash? Are we waiting for
> something in a sync manner that should be async?
> --
>
> https://code.launchpad.net/~nick-dedekind/unity8/inline-dash-videos/+merge/260251
> You are reviewing the proposed merge of
> lp:~nick-dedekind/unity8/inline-dash-videos into lp:unity8.
>

Revision history for this message
Albert Astals Cid (aacid) wrote :

> Albert: Can you get a back trace of all of the threads plus a threads info
> listing and the media-hub.log for the crash?

Jim: here it comes

bt of threads: http://paste.ubuntu.com/11878137/
media-hub.log: http://paste.ubuntu.com/11878149/

Revision history for this message
Jim Hodapp (jhodapp) wrote :

That looks good, thanks Albert

On Tue, Jul 14, 2015 at 11:12 AM, Albert Astals Cid <
<email address hidden>> wrote:

> > Albert: Can you get a back trace of all of the threads plus a threads
> info
> > listing and the media-hub.log for the crash?
>
> Jim: here it comes
>
> bt of threads: http://paste.ubuntu.com/11878137/
> media-hub.log: http://paste.ubuntu.com/11878149/
> --
>
> https://code.launchpad.net/~nick-dedekind/unity8/inline-dash-videos/+merge/260251
> You are reviewing the proposed merge of
> lp:~nick-dedekind/unity8/inline-dash-videos into lp:unity8.
>

Revision history for this message
Albert Astals Cid (aacid) wrote :

Text conflict in tests/qmltests/Dash/Previews/tst_PreviewWidgetFactory.qml
1 conflicts encountered.

review: Needs Fixing
Revision history for this message
Albert Astals Cid (aacid) wrote :

You can probably remove
   item.currentIndex = subPageLoader.initialIndex;

Once we merge
    https://code.launchpad.net/~mardy/unity8/lp1433442/+merge/268068

review: Needs Fixing
Revision history for this message
Michael Zanetti (mzanetti) wrote :

mardy's branch is merged.

Revision history for this message
Albert Astals Cid (aacid) wrote :

Please propose against overlay

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Albert Astals Cid (aacid) wrote :

Ignore the proposing to overlay, we don't use it anymore, you still have conflicts though

Text conflict in qml/Dash/GenericScopeView.qml
Text conflict in tests/mocks/QtMultimedia/mediaplayer.cpp
Text conflict in tests/qmltests/Dash/Previews/tst_PreviewWidgetFactory.qml
3 conflicts encountered.

review: Needs Fixing
Revision history for this message
Michał Sawicz (saviq) wrote :

Text conflict in qml/Dash/GenericScopeView.qml
Text conflict in tests/mocks/QtMultimedia/mediaplayer.cpp
Text conflict in tests/qmltests/Dash/Previews/tst_PreviewWidgetFactory.qml
3 conflicts encountered.

review: Needs Fixing

Unmerged revisions

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'qml/Components/LazyImage.qml'
2--- qml/Components/LazyImage.qml 2014-09-29 20:55:54 +0000
3+++ qml/Components/LazyImage.qml 2015-07-10 14:23:36 +0000
4@@ -29,6 +29,7 @@
5
6 property real initialWidth: scaleTo == "width" || scaleTo == "fit" ? width : units.gu(10)
7 property real initialHeight: scaleTo == "height" || scaleTo == "fit" ? height : units.gu(10)
8+ property real lastScaledDimension: scaleTo == "height" || scaleTo == "fit" ? width : height
9
10 property alias sourceSize: image.sourceSize
11 property alias fillMode: image.fillMode
12@@ -157,6 +158,7 @@
13 easing.type: Easing.Linear; duration: UbuntuAnimation.SnapDuration
14 }
15 }
16+ ScriptAction { script: { lastScaledDimension = scaleTo == "height" || scaleTo == "fit" ? root.width : root.height } }
17 }
18 },
19
20
21=== added directory 'qml/Components/MediaServices'
22=== added file 'qml/Components/MediaServices/MediaServices.qml'
23--- qml/Components/MediaServices/MediaServices.qml 1970-01-01 00:00:00 +0000
24+++ qml/Components/MediaServices/MediaServices.qml 2015-07-10 14:23:36 +0000
25@@ -0,0 +1,391 @@
26+/*
27+ * Copyright (C) 2015 Canonical, Ltd.
28+ *
29+ * This program is free software; you can redistribute it and/or modify
30+ * it under the terms of the GNU General Public License as published by
31+ * the Free Software Foundation; version 3.
32+ *
33+ * This program is distributed in the hope that it will be useful,
34+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
35+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
36+ * GNU General Public License for more details.
37+ *
38+ * You should have received a copy of the GNU General Public License
39+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
40+ */
41+
42+import QtQuick 2.0
43+import QtMultimedia 5.0
44+import Ubuntu.Components 1.2
45+
46+FocusScope {
47+ id: root
48+ property var sourceData
49+ property string context: ""
50+ property list<Action> actions
51+ property Item rootItem: QuickUtils.rootItem(root)
52+ property var maximumEmbeddedHeight
53+
54+ property alias header: headerContent.item
55+ property alias content: contentLoader.item
56+ property alias footer: footerContent.item
57+ property alias fullscreen: priv.fullscreen
58+
59+ signal close();
60+
61+ onFullscreenChanged: {
62+ if (!fullscreen) rotationAction.checked = false;
63+ }
64+
65+ OrientationHelper {
66+ id: orientationHelper
67+ automaticOrientation: fullscreen
68+
69+ StateGroup {
70+ id: orientationState
71+
72+ states: [
73+ State {
74+ name: "portrait"
75+ when: !rotationAction.checked
76+ },
77+ State {
78+ name: "landscape"
79+ when: rotationAction.checked
80+ }
81+ ]
82+
83+ transitions: [
84+ Transition {
85+ to: "landscape"
86+ SequentialAnimation {
87+ PropertyAction { target: orientationHelper; property: "automaticOrientation"; value: false }
88+ PropertyAction { target: orientationHelper; property: "orientationAngle"; value: 90 }
89+ PropertyAction { target: orientationHelper; property: "automaticOrientation"; value: false }
90+ }
91+ },
92+ Transition {
93+ to: "portrait"
94+ SequentialAnimation {
95+ PropertyAction { target: orientationHelper; property: "automaticOrientation"; value: false }
96+ PropertyAction { target: orientationHelper; property: "orientationAngle"; value: 0 }
97+ PropertyAction { target: orientationHelper; property: "automaticOrientation"; value: fullscreen }
98+ }
99+ }
100+ ]
101+ }
102+
103+ Loader {
104+ id: contentLoader
105+
106+ sourceComponent: {
107+ switch (context) {
108+ case "video":
109+ return videoComponent;
110+ }
111+ return undefined;
112+ }
113+
114+ Rectangle {
115+ anchors.fill: parent
116+ color: "#1B1B1B"
117+ opacity: 0.85
118+ }
119+
120+ Component {
121+ id: videoComponent
122+ VideoPlayer {
123+ id: player
124+ objectName: "videoPlayer"
125+
126+ width: orientationHelper.width
127+ height: orientationHelper.height
128+ maximumEmbeddedHeight: root.maximumEmbeddedHeight
129+ fixedHeight: fullscreen
130+ orientation: orientationState.state == "landscape" ? Qt.LandscapeOrientation : Qt.PortraitOrientation
131+
132+ screenshot: {
133+ var screenshot = root.sourceData["screenshot"];
134+ if (screenshot) return screenshot;
135+
136+ var source = root.sourceData["source"];
137+ if (source) {
138+ if (source.toString().indexOf("file://") === 0) {
139+ return "image://thumbnailer/" + source.toString().substr(7);
140+ }
141+ }
142+ return "";
143+ }
144+
145+ mediaPlayer: footer ? footer.mediaPlayer : null
146+
147+ onClicked: {
148+ if (mediaPlayer.availability !== MediaPlayer.Available) return;
149+
150+ if (mediaPlayer.playbackState === MediaPlayer.StoppedState) {
151+ mediaPlayer.play();
152+ } else if (controlHideTimer.running) {
153+ if (mediaPlayer.playbackState === MediaPlayer.PlayingState) {
154+ mediaPlayer.pause();
155+ } else {
156+ mediaPlayer.play();
157+ }
158+ } else {
159+ controlHideTimer.restart();
160+ }
161+ }
162+ onPositionChanged: controlHideTimer.restart();
163+ }
164+ }
165+ }
166+
167+ Loader {
168+ id: headerContent
169+ anchors {
170+ top: contentLoader.top
171+ left: parent.left
172+ right: parent.right
173+ topMargin: -units.gu(6)
174+ }
175+ height: units.gu(6)
176+ visible: false
177+
178+ // eater
179+ MouseArea {
180+ anchors.fill: parent
181+ }
182+
183+ Rectangle {
184+ anchors.fill: parent
185+ color: "#1B1B1B"
186+ opacity: 0.85
187+ visible: headerContent.status === Loader.Ready
188+ }
189+
190+ sourceComponent: root.fullscreen ? headerComponent : undefined
191+
192+ Component {
193+ id: headerComponent
194+
195+ MediaServicesHeader {
196+ onGoPrevious: {
197+ rotationAction.checked = false;
198+ root.close();
199+ }
200+
201+ component: {
202+ switch (context) {
203+ case "video":
204+ break;
205+ }
206+ return undefined;
207+ }
208+ }
209+ }
210+
211+ // If we interact with the bar, reset the hide timer.
212+ MouseArea {
213+ anchors.fill: parent
214+ onPressed: {
215+ mouse.accepted = false
216+ if (controlHideTimer.running) controlHideTimer.restart()
217+ }
218+ }
219+ }
220+
221+ Loader {
222+ id: footerContent
223+ anchors {
224+ left: parent.left
225+ right: parent.right
226+ bottom: contentLoader.bottom
227+ bottomMargin: -units.gu(7)
228+ }
229+ height: units.gu(7)
230+ visible: false
231+
232+ sourceComponent: {
233+ switch (context) {
234+ case "video":
235+ return videoControlsComponent;
236+ }
237+ return undefined;
238+ }
239+ // eater
240+ MouseArea {
241+ anchors.fill: parent
242+ }
243+
244+ Rectangle {
245+ anchors.fill: parent
246+ color: "#1B1B1B"
247+ opacity: 0.85
248+ visible: footerContent.status === Loader.Ready
249+ }
250+
251+ Component {
252+ id: videoControlsComponent
253+ VideoPlayerControls {
254+ id: controls
255+ objectName: "videoControls"
256+
257+ viewAction: rotationAction.enabled ? rotationAction : fullscreenAction
258+ userActions: root.actions
259+
260+ mediaPlayer.source: {
261+ if (!root.sourceData) return "";
262+
263+ var x = root.sourceData["source"];
264+ if (x.toString().indexOf("video://") === 0) {
265+ return x.toString().substr(6);
266+ }
267+ return x;
268+ }
269+ mediaPlayer.onPlaybackStateChanged: {
270+ controlHideTimer.restart();
271+ }
272+
273+ Binding {
274+ target: priv
275+ property: "forceControlsShown"
276+
277+ value: (fullscreen && mediaPlayer.playbackState === MediaPlayer.StoppedState) ||
278+ mediaPlayer.playbackState === MediaPlayer.PausedState ||
279+ interacting
280+ }
281+
282+ onInteractingChanged: {
283+ controlHideTimer.restart();
284+ }
285+
286+ Binding {
287+ target: header
288+ property: "title"
289+ value: controls.mediaPlayer.metaData.title !== undefined ?
290+ controls.mediaPlayer.metaData.title :
291+ controls.mediaPlayer.source.toString().replace(/^.*[\\\/]/, '')
292+ when: header != null
293+ }
294+ }
295+ }
296+
297+ // If we interact with the bar, reset the hide timer.
298+ MouseArea {
299+ z: 1
300+ anchors.fill: parent
301+ onPressed: {
302+ mouse.accepted = false
303+ if (controlHideTimer.running) controlHideTimer.restart()
304+ }
305+ }
306+ }
307+ }
308+
309+ StateGroup {
310+ states: [
311+ State {
312+ name: "controlsShown"
313+ when: priv.forceControlsShown || priv.controlTimerActive
314+ PropertyChanges {
315+ target: footerContent
316+ anchors.bottomMargin: 0
317+ visible: true
318+ }
319+ PropertyChanges {
320+ target: headerContent
321+ anchors.topMargin: 0
322+ visible: true
323+ }
324+ }
325+ ]
326+
327+ transitions: [
328+ Transition {
329+ to: "controlsShown"
330+ SequentialAnimation {
331+ PropertyAction { target: root; property: "clip"; value: true }
332+ PropertyAction { property: "visible" }
333+ NumberAnimation {
334+ properties: "anchors.bottomMargin,anchors.topMargin"
335+ duration: UbuntuAnimation.FastDuration
336+ }
337+ PropertyAction { target: root; property: "clip"; value: false }
338+ }
339+ },
340+ Transition {
341+ from: "controlsShown"
342+ SequentialAnimation {
343+ PropertyAction { target: root; property: "clip"; value: true }
344+ NumberAnimation {
345+ properties: "anchors.bottomMargin,anchors.topMargin"
346+ duration: UbuntuAnimation.FastDuration
347+ }
348+ PropertyAction { property: "visible" }
349+ PropertyAction { target: root; property: "clip"; value: false }
350+ }
351+ }
352+ ]
353+ }
354+
355+ StateGroup {
356+ states: [
357+ State {
358+ name: "minimized"
359+ when: !priv.fullscreen
360+ PropertyChanges { target: root; implicitHeight: content ? content.implicitHeight : 0; }
361+ },
362+ State {
363+ name: "fullscreen"
364+ when: priv.fullscreen
365+ ParentChange { target: root; parent: rootItem; x: 0; y: 0; width: parent.width; }
366+ PropertyChanges { target: root; implicitHeight: root.parent ? root.parent.height : 0; }
367+ PropertyChanges { target: fullscreenAction; iconName: "view-restore" }
368+ }
369+ ]
370+
371+ transitions: Transition {
372+ ParentAnimation {
373+ UbuntuNumberAnimation { properties: "x,y,width,implicitHeight"; duration: UbuntuAnimation.FastDuration }
374+ }
375+ }
376+ }
377+
378+ QtObject {
379+ id: priv
380+
381+ property bool fullscreen: false
382+ property bool controlTimerActive: false
383+ property bool forceControlsShown: false
384+ }
385+
386+ Timer {
387+ id: controlHideTimer
388+ objectName: "controlHideTimer"
389+ interval: 4000
390+ running: false
391+
392+ onRunningChanged: {
393+ if (running) {
394+ priv.controlTimerActive = true;
395+ }
396+ }
397+ onTriggered: priv.controlTimerActive = false;
398+ }
399+
400+ Action {
401+ id: fullscreenAction
402+ enabled: rotationAction.enabled === false
403+ iconName: "view-fullscreen"
404+
405+ onTriggered: priv.fullscreen = !priv.fullscreen
406+ }
407+
408+ Action {
409+ id: rotationAction
410+ enabled: root.fullscreen === true
411+ iconName: "orientation-lock"
412+
413+ property bool checked: false
414+ onTriggered: checked = !checked
415+ }
416+}
417
418=== added file 'qml/Components/MediaServices/MediaServicesControls.qml'
419--- qml/Components/MediaServices/MediaServicesControls.qml 1970-01-01 00:00:00 +0000
420+++ qml/Components/MediaServices/MediaServicesControls.qml 2015-07-10 14:23:36 +0000
421@@ -0,0 +1,189 @@
422+/*
423+ * Copyright (C) 2015 Canonical, Ltd.
424+ *
425+ * This program is free software; you can redistribute it and/or modify
426+ * it under the terms of the GNU General Public License as published by
427+ * the Free Software Foundation; version 3.
428+ *
429+ * This program is distributed in the hope that it will be useful,
430+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
431+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
432+ * GNU General Public License for more details.
433+ *
434+ * You should have received a copy of the GNU General Public License
435+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
436+ */
437+
438+import QtQuick 2.0
439+import QtQuick.Layouts 1.1
440+import QtMultimedia 5.0
441+import Ubuntu.Components 1.2
442+import Ubuntu.Components.ListItems 1.0 as ListItem
443+
444+Item {
445+ id: root
446+
447+ property alias component: loader.sourceComponent
448+
449+ signal actionClicked(string action)
450+ signal viewModeClicked
451+
452+ property list<Action> userActions
453+ property Action viewAction
454+
455+ RowLayout {
456+ anchors {
457+ left: parent.left
458+ right: parent.right
459+ }
460+ anchors.verticalCenter: parent.verticalCenter
461+ anchors.margins: units.gu(2)
462+ spacing: units.gu(2)
463+
464+ AbstractButton {
465+ id: actionButton
466+ Layout.preferredWidth: units.gu(3)
467+ Layout.preferredHeight: units.gu(3)
468+ Layout.alignment: Qt.AlignVCenter
469+ enabled: action && action.enabled
470+
471+ Action {
472+ id: popupAction
473+ iconName: "navigation-menu"
474+ onTriggered: userActionsPopup.createObject(root, { "anchors.bottom": root.top })
475+ }
476+
477+ action: {
478+ switch (userActions.length) {
479+ case 0:
480+ return null;
481+ case 1:
482+ return userActions[0];
483+ default:
484+ return popupAction;
485+ }
486+ }
487+
488+ Icon {
489+ anchors.fill: parent
490+ visible: actionButton.action && actionButton.action.iconSource !== "" || false
491+ source: actionButton.action ? actionButton.action.iconSource : ""
492+ color: "#F3F3E7"
493+ opacity: actionButton.action && actionButton.action.enabled ? 1.0 : 0.5
494+ }
495+ }
496+
497+ Loader {
498+ id: loader
499+ Layout.fillWidth: true
500+ Layout.preferredHeight: units.gu(3)
501+ }
502+
503+ AbstractButton {
504+ objectName: "viewActionButton"
505+ Layout.preferredWidth: units.gu(3)
506+ Layout.preferredHeight: units.gu(3)
507+ Layout.alignment: Qt.AlignVCenter
508+ enabled: viewAction.enabled
509+ action: viewAction
510+
511+ Icon {
512+ anchors.fill: parent
513+ visible: viewAction.iconSource !== ""
514+ source: viewAction.iconSource
515+ color: "#F3F3E7"
516+ opacity: viewAction.enabled ? 1.0 : 0.5
517+ }
518+ }
519+ }
520+
521+ Component {
522+ id: userActionsPopup
523+
524+ Rectangle {
525+ id: popup
526+ color: "#1B1B1B"
527+ width: userActionsColumn.width
528+ height: userActionsColumn.height
529+
530+ InverseMouseArea {
531+ id: eventGrabber
532+ acceptedButtons: Qt.LeftButton | Qt.MiddleButton | Qt.RightButton
533+ anchors.fill: popup
534+ propagateComposedEvents: false
535+ onWheel: wheel.accepted = true
536+
537+ onPressed: popup.destroy()
538+ }
539+
540+ Column {
541+ id: userActionsColumn
542+ spacing: units.gu(1)
543+ width: units.gu(31)
544+
545+ Repeater {
546+ id: actionRepeater
547+ model: userActions
548+ AbstractButton {
549+ action: modelData
550+
551+ onClicked: popup.destroy()
552+
553+ implicitHeight: units.gu(4) + bottomDividerLine.height
554+ width: parent ? parent.width : units.gu(31)
555+
556+ Rectangle {
557+ visible: parent.pressed
558+ anchors {
559+ left: parent.left
560+ right: parent.right
561+ top: parent.top
562+ }
563+ height: parent.height - bottomDividerLine.height
564+ opacity: 0.5
565+ }
566+
567+ Icon {
568+ id: actionIcon
569+ visible: "" !== action.iconSource
570+ source: action.iconSource
571+ color: "#F3F3E7"
572+ anchors {
573+ verticalCenter: parent.verticalCenter
574+ verticalCenterOffset: units.dp(-1)
575+ left: parent.left
576+ leftMargin: units.gu(2)
577+ }
578+ width: units.gu(2)
579+ height: units.gu(2)
580+ opacity: action.enabled ? 1.0 : 0.5
581+ }
582+
583+ Label {
584+ anchors {
585+ verticalCenter: parent.verticalCenter
586+ verticalCenterOffset: units.dp(-1)
587+ left: actionIcon.visible ? actionIcon.right : parent.left
588+ leftMargin: units.gu(2)
589+ right: parent.right
590+ }
591+ // In the tabs overflow panel there are no icons, and the font-size
592+ // is medium as opposed to the small font-size in the actions overflow panel.
593+ fontSize: actionIcon.visible ? "small" : "medium"
594+ elide: Text.ElideRight
595+ text: action.text
596+ color: "#F3F3E7"
597+ opacity: action.enabled ? 1.0 : 0.5
598+ }
599+
600+ ListItem.ThinDivider {
601+ id: bottomDividerLine
602+ anchors.bottom: parent.bottom
603+ visible: index !== actionRepeater.count - 1
604+ }
605+ }
606+ }
607+ }
608+ }
609+ }
610+}
611
612=== added file 'qml/Components/MediaServices/MediaServicesHeader.qml'
613--- qml/Components/MediaServices/MediaServicesHeader.qml 1970-01-01 00:00:00 +0000
614+++ qml/Components/MediaServices/MediaServicesHeader.qml 2015-07-10 14:23:36 +0000
615@@ -0,0 +1,72 @@
616+/*
617+ * Copyright (C) 2015 Canonical, Ltd.
618+ *
619+ * This program is free software; you can redistribute it and/or modify
620+ * it under the terms of the GNU General Public License as published by
621+ * the Free Software Foundation; version 3.
622+ *
623+ * This program is distributed in the hope that it will be useful,
624+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
625+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
626+ * GNU General Public License for more details.
627+ *
628+ * You should have received a copy of the GNU General Public License
629+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
630+ */
631+
632+import QtQuick 2.3
633+import QtQuick.Layouts 1.1
634+import Ubuntu.Components 1.2
635+
636+Item {
637+ id: root
638+
639+ property alias title: _title.text
640+ property alias component: loader.sourceComponent
641+
642+ signal goPrevious
643+
644+ RowLayout {
645+ id: row
646+ anchors {
647+ left: parent.left
648+ right: parent.right
649+ }
650+ anchors.verticalCenter: parent.verticalCenter
651+ anchors.margins: units.gu(2)
652+ spacing: units.gu(2)
653+
654+ // eater
655+ AbstractButton {
656+ id: navigationButton
657+ objectName: "navigationButton"
658+ Layout.preferredWidth: units.gu(3)
659+ Layout.preferredHeight: units.gu(3)
660+ Layout.alignment: Qt.AlignVCenter
661+
662+ Icon {
663+ anchors.fill: parent
664+ name: "go-previous"
665+ color: "#F3F3E7"
666+ }
667+
668+ onTriggered: {
669+ root.goPrevious();
670+ }
671+ }
672+
673+ Label {
674+ id: _title
675+ Layout.fillWidth: true
676+ Layout.alignment: Qt.AlignVCenter
677+ elide: Text.ElideRight
678+ color: "#F3F3E7"
679+ }
680+
681+ Loader {
682+ id: loader
683+ Layout.alignment: Qt.AlignVCenter
684+ Layout.preferredHeight: units.gu(3)
685+ }
686+ }
687+}
688
689=== added file 'qml/Components/MediaServices/VideoPlayer.qml'
690--- qml/Components/MediaServices/VideoPlayer.qml 1970-01-01 00:00:00 +0000
691+++ qml/Components/MediaServices/VideoPlayer.qml 2015-07-10 14:23:36 +0000
692@@ -0,0 +1,152 @@
693+/*
694+ * Copyright (C) 2015 Canonical, Ltd.
695+ *
696+ * This program is free software; you can redistribute it and/or modify
697+ * it under the terms of the GNU General Public License as published by
698+ * the Free Software Foundation; version 3.
699+ *
700+ * This program is distributed in the hope that it will be useful,
701+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
702+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
703+ * GNU General Public License for more details.
704+ *
705+ * You should have received a copy of the GNU General Public License
706+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
707+ */
708+
709+import QtQuick 2.0
710+import QtMultimedia 5.0
711+import Ubuntu.Components 0.1
712+import Ubuntu.Thumbnailer 0.1
713+import "../../Components"
714+
715+Item {
716+ id: root
717+
718+ property alias screenshot: image.source
719+ property alias mediaPlayer: videoOutput.source
720+ property int orientation: Qt.PortraitOrientation
721+ property bool fixedHeight: false
722+ property var maximumEmbeddedHeight
723+
724+ implicitHeight: {
725+ if (parent && orientation == Qt.LandscapeOrientation) {
726+ return parent.height;
727+ }
728+ return content.height;
729+ }
730+
731+ signal clicked
732+ signal positionChanged
733+
734+ Item {
735+ id: content
736+ anchors {
737+ left: parent.left
738+ right: parent.right
739+ verticalCenter: parent.verticalCenter
740+ }
741+ height: {
742+ if (root.orientation == Qt.LandscapeOrientation || fixedHeight) {
743+ return root.height;
744+ }
745+ var proposedHeight = videoOutput.height;
746+ if (maximumEmbeddedHeight !== undefined && maximumEmbeddedHeight < proposedHeight) {
747+ return maximumEmbeddedHeight;
748+ }
749+ return proposedHeight;
750+ }
751+ clip: image.height > videoOutput.height
752+
753+ LazyImage {
754+ id: image
755+ objectName: "screenshot"
756+ anchors {
757+ left: parent.left
758+ right: parent.right
759+ }
760+ anchors.verticalCenter: parent.verticalCenter
761+ scaleTo: "width"
762+ lastScaledDimension: playButton.height + units.gu(2)
763+ initialHeight: lastScaledDimension
764+
765+ visible: !mediaPlayer || mediaPlayer.playbackState === MediaPlayer.StoppedState
766+ }
767+
768+ VideoOutput {
769+ id: videoOutput
770+ anchors.centerIn: parent
771+
772+ width: root.width
773+ height: {
774+ if (fixedHeight) {
775+ return root.height;
776+ }
777+ var proposedHeight = mediaPlayer && mediaPlayer.metaData.resolution !== undefined ?
778+ (mediaPlayer.metaData.resolution.height / mediaPlayer.metaData.resolution.width) * width :
779+ image.height;
780+ if (maximumEmbeddedHeight !== undefined && maximumEmbeddedHeight < proposedHeight) {
781+ return maximumEmbeddedHeight;
782+ }
783+ return proposedHeight;
784+ }
785+
786+ source: mediaPlayer
787+ visible: mediaPlayer && mediaPlayer.playbackState !== MediaPlayer.StoppedState || false
788+
789+ Connections {
790+ target: mediaPlayer
791+ onError: {
792+ if (error !== MediaPlayer.NoError) {
793+ errorTimer.restart();
794+ }
795+ }
796+ }
797+ }
798+ }
799+
800+ Rectangle {
801+ id: playButton
802+ readonly property bool bigButton: parent.width > units.gu(40)
803+ anchors.centerIn: content
804+ width: bigButton ? units.gu(10) : units.gu(8)
805+ height: width
806+ visible: mediaPlayer && mediaPlayer.playbackState !== MediaPlayer.PlayingState || false
807+ color: "#1B1B1B"
808+ opacity: 0.85
809+ radius: width/2
810+
811+ Behavior on width { UbuntuNumberAnimation {} }
812+
813+ Icon {
814+ anchors.fill: parent
815+ anchors.margins: units.gu(1)
816+ name: errorTimer.running ? "dialog-warning-symbolic" : "media-playback-start"
817+ color: "#F3F3E7"
818+ }
819+ }
820+
821+ ActivityIndicator {
822+ anchors.centerIn: content
823+ running: {
824+ if (!mediaPlayer) return false;
825+ return mediaPlayer.status === MediaPlayer.Stalled ||
826+ (mediaPlayer.playbackState === MediaPlayer.PlayingState && mediaPlayer.status === MediaPlayer.Loading);
827+ }
828+ }
829+
830+ MouseArea {
831+ id: contentMouseArea
832+ anchors.fill: content
833+ enabled: !errorTimer.running
834+ hoverEnabled: mediaPlayer && mediaPlayer.playbackState !== MediaPlayer.StoppedState || false
835+
836+ onClicked: root.clicked()
837+ onPositionChanged: root.positionChanged()
838+ }
839+
840+ Timer {
841+ id: errorTimer
842+ interval: 2000
843+ }
844+}
845
846=== added file 'qml/Components/MediaServices/VideoPlayerControls.qml'
847--- qml/Components/MediaServices/VideoPlayerControls.qml 1970-01-01 00:00:00 +0000
848+++ qml/Components/MediaServices/VideoPlayerControls.qml 2015-07-10 14:23:36 +0000
849@@ -0,0 +1,182 @@
850+/*
851+ * Copyright (C) 2015 Canonical, Ltd.
852+ *
853+ * This program is free software; you can redistribute it and/or modify
854+ * it under the terms of the GNU General Public License as published by
855+ * the Free Software Foundation; version 3.
856+ *
857+ * This program is distributed in the hope that it will be useful,
858+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
859+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
860+ * GNU General Public License for more details.
861+ *
862+ * You should have received a copy of the GNU General Public License
863+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
864+ */
865+
866+import QtQuick 2.0
867+import QtQuick.Layouts 1.1
868+import QtMultimedia 5.0
869+import Ubuntu.Components 1.2
870+
871+MediaServicesControls {
872+ id: root
873+ readonly property alias mediaPlayer: _mediaPlayer
874+ property bool interacting: false
875+
876+ QtObject {
877+ id: priv
878+
879+ function formatProgress(time) {
880+ time = Math.floor(time / 1000);
881+
882+ var secs = time % 60;
883+ time = Math.floor(time / 60);
884+ var min = time % 60;
885+ var hour = Math.floor(time / 60);
886+
887+ if (secs < 10) secs = "0%1".arg(secs);
888+ if (min < 10) min = "0%1".arg(min);
889+ if (hour > 0) {
890+ // TRANSLATORS: this refers to a duration/remaining time of the video in hours, minutes and seconds,
891+ // of which you can change the order.
892+ // %1 refers to hours, %2 refers to minutes and %3 refers to seconds.
893+ return i18n.tr("%1:%2:%3").arg(hour).arg(min).arg(secs);
894+ } else {
895+ // TRANSLATORS: this refers to a duration/remaining time of the video in minutes and seconds,
896+ // of which you can change the order.
897+ // %1 refers to minutes and %2 refers to seconds.
898+ return i18n.tr("%1:%2").arg(min).arg(secs);
899+ }
900+ }
901+ }
902+
903+ component: Item {
904+ Connections {
905+ target: mediaPlayer
906+ onPositionChanged: {
907+ if (slider.valueGuard) return;
908+
909+ slider.valueGuard = true;
910+ slider.value = mediaPlayer.position;
911+ slider.valueGuard = false;
912+ if (!slider.pressed) {
913+ positionLabel.text = priv.formatProgress(mediaPlayer.position);
914+ }
915+ }
916+ }
917+
918+ Binding {
919+ target: root
920+ property: "interacting"
921+ value: slider.pressed
922+ }
923+
924+ Label {
925+ id: positionLabel
926+ anchors {
927+ left: parent.left
928+ bottom: parent.bottom
929+ bottomMargin: -units.dp(3)
930+ }
931+ verticalAlignment: Text.AlignBottom
932+ fontSize: "x-small"
933+ color: "#F3F3E7"
934+
935+ text: priv.formatProgress(mediaPlayer.position)
936+ }
937+
938+ Slider {
939+ id: slider
940+ property bool valueGuard: false
941+
942+ anchors {
943+ left: parent.left
944+ right: parent.right
945+ }
946+ height: units.gu(2)
947+ live: true
948+ enabled: mediaPlayer.seekable && mediaPlayer.duration > 0
949+ minimumValue: 0
950+ maximumValue: mediaPlayer.duration > 0 ? mediaPlayer.duration : 1
951+ value: mediaPlayer.position
952+
953+ onStyleInstanceChanged: {
954+ if (__styleInstance) __styleInstance.backgroundColor = "#F3F3E7";
955+ }
956+
957+ onValueChanged: {
958+ if (!pressed) return;
959+ if (slider.valueGuard) return;
960+
961+ slider.valueGuard = true;
962+ mediaPlayer.seek(value);
963+ slider.valueGuard = false;
964+ }
965+
966+ property bool wasPlaying: mediaPlayer.playbackState === MediaPlayer.PlayingState
967+ onPressedChanged: {
968+ if (pressed) {
969+ wasPlaying = mediaPlayer.playbackState === MediaPlayer.PlayingState
970+ mediaPlayer.pause();
971+ } else {
972+
973+ positionLabel.text = priv.formatProgress(mediaPlayer.position);
974+ if (wasPlaying) {
975+ mediaPlayer.play();
976+ }
977+ }
978+ }
979+
980+ function formatValue(value) {
981+ return priv.formatProgress(value);
982+ }
983+ }
984+
985+ Label {
986+ anchors {
987+ right: parent.right
988+ bottom: parent.bottom
989+ bottomMargin: -units.dp(3)
990+ }
991+ verticalAlignment: Text.AlignBottom
992+ fontSize: "x-small"
993+ color: "#F3F3E7"
994+
995+ text: priv.formatProgress(mediaPlayer.duration)
996+ }
997+ }
998+
999+ function getPlaybackState(playbackState) {
1000+ if (playbackState === MediaPlayer.PlayingState) return "PlayingState";
1001+ else if (playbackState === MediaPlayer.PausedState) return "PausedState";
1002+ else if (playbackState === MediaPlayer.StoppedState) return "StoppedState";
1003+ return "";
1004+ }
1005+
1006+ function getPlayerStatus(status) {
1007+ if (status === MediaPlayer.NoMedia) return "NoMedia";
1008+ else if (status === MediaPlayer.Loading) return "Loading";
1009+ else if (status === MediaPlayer.Loaded) return "Loaded";
1010+ else if (status === MediaPlayer.Buffering) return "Buffering";
1011+ else if (status === MediaPlayer.Stalled) return "Stalled";
1012+ else if (status === MediaPlayer.Buffered) return "Buffered";
1013+ else if (status === MediaPlayer.EndOfMedia) return "EndOfMedia";
1014+ else if (status === MediaPlayer.InvalidMedia) return "InvalidMedia";
1015+ else if (status === MediaPlayer.UnknownStatus) return "UnknownStatus";
1016+ return "";
1017+ }
1018+
1019+ MediaPlayer {
1020+ id: _mediaPlayer
1021+ objectName: "mediaPlayer"
1022+
1023+ onError: {
1024+ if (error !== MediaPlayer.NoError) {
1025+ stop();
1026+ }
1027+ }
1028+// onPlaybackStateChanged: console.log("PLAYBACK STATE CHANGED", getPlaybackState(playbackState));
1029+// onStatusChanged: console.log("PLAYER STATUS CHANGED", getPlayerStatus(status));
1030+ }
1031+}
1032
1033=== modified file 'qml/Dash/GenericScopeView.qml'
1034--- qml/Dash/GenericScopeView.qml 2015-07-03 14:24:08 +0000
1035+++ qml/Dash/GenericScopeView.qml 2015-07-10 14:23:36 +0000
1036@@ -762,6 +762,7 @@
1037 item.open = Qt.binding(function() { return subPageLoader.open; } )
1038 item.initialIndex = Qt.binding(function() { return subPageLoader.initialIndex; } )
1039 item.model = Qt.binding(function() { return subPageLoader.model; } )
1040+ item.currentIndex = subPageLoader.initialIndex;
1041 }
1042 open = true;
1043 }
1044
1045=== added file 'qml/Dash/Previews/PreviewInlineVideo.qml'
1046--- qml/Dash/Previews/PreviewInlineVideo.qml 1970-01-01 00:00:00 +0000
1047+++ qml/Dash/Previews/PreviewInlineVideo.qml 2015-07-10 14:23:36 +0000
1048@@ -0,0 +1,89 @@
1049+/*
1050+ * Copyright (C) 2015 Canonical, Ltd.
1051+ *
1052+ * This program is free software; you can redistribute it and/or modify
1053+ * it under the terms of the GNU General Public License as published by
1054+ * the Free Software Foundation; version 3.
1055+ *
1056+ * This program is distributed in the hope that it will be useful,
1057+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1058+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1059+ * GNU General Public License for more details.
1060+ *
1061+ * You should have received a copy of the GNU General Public License
1062+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1063+ */
1064+
1065+import QtQuick 2.0
1066+import QtMultimedia 5.0
1067+import Ubuntu.Components 1.2
1068+import Ubuntu.Thumbnailer 0.1
1069+//import Ubuntu.Content 0.1
1070+import "../../Components/MediaServices"
1071+
1072+/*! \brief Preview widget for video.
1073+
1074+ This widget shows video contained in widgetData["source"],
1075+ with a placeholder screenshot specified by widgetData["screenshot"].
1076+ */
1077+
1078+PreviewWidget {
1079+ id: root
1080+ implicitWidth: units.gu(35)
1081+ implicitHeight: services.height
1082+
1083+ property alias rootItem: services.rootItem
1084+
1085+ MediaServices {
1086+ id: services
1087+ width: parent.width
1088+
1089+ context: "video"
1090+ sourceData: root.isCurrentPreview ? widgetData : undefined
1091+ fullscreen: false
1092+ maximumEmbeddedHeight: rootItem.height / 2
1093+
1094+ onClose: fullscreen = false
1095+
1096+// actions: [
1097+// Action {
1098+// text: i18n.tr("Share")
1099+// iconSource: "image://theme/share"
1100+// onTriggered: sharePicker.visible = true
1101+// }
1102+// ]
1103+ }
1104+
1105+// Component {
1106+// id: contentItemComp
1107+// ContentItem {
1108+// url: widgetData["source"]
1109+// }
1110+// }
1111+// ContentPeerPicker {
1112+// id: sharePicker
1113+// objectName: "sharePickerEvents"
1114+// anchors.fill: parent
1115+// showTitle: false
1116+// visible: false
1117+// parent: rootItem
1118+// z: 100
1119+
1120+// contentType: ContentType.Videos
1121+// handler: ContentHandler.Share
1122+
1123+// onPeerSelected: {
1124+// visible = false;
1125+
1126+// var curTransfer = peer.request();
1127+// if (curTransfer.state === ContentTransfer.InProgress)
1128+// {
1129+// var medias = [ contentItemComp.createObject(parent) ]
1130+// curTransfer.state = ContentTransfer.Charged;
1131+// }
1132+// }
1133+// onCancelPressed: {
1134+// visible = false;
1135+// }
1136+// }
1137+}
1138
1139=== modified file 'qml/Dash/Previews/PreviewWidgetFactory.qml'
1140--- qml/Dash/Previews/PreviewWidgetFactory.qml 2015-05-08 13:12:02 +0000
1141+++ qml/Dash/Previews/PreviewWidgetFactory.qml 2015-07-10 14:23:36 +0000
1142@@ -61,7 +61,14 @@
1143 case "reviews": return "PreviewRatingDisplay.qml";
1144 case "table": return "PreviewTable.qml";
1145 case "text": return "PreviewTextSummary.qml";
1146- case "video": return "PreviewVideoPlayback.qml";
1147+ case "video": {
1148+ if (!widgetData) return "";
1149+ var source = widgetData.hasOwnProperty("source") ? widgetData["source"].toString() : "";
1150+ if (source.match("^http") !== null) {
1151+ return "PreviewVideoPlayback.qml";
1152+ }
1153+ return "PreviewInlineVideo.qml";
1154+ }
1155 default: return "";
1156 }
1157 }
1158
1159=== modified file 'tests/mocks/QtMultimedia/CMakeLists.txt'
1160--- tests/mocks/QtMultimedia/CMakeLists.txt 2014-05-02 22:57:21 +0000
1161+++ tests/mocks/QtMultimedia/CMakeLists.txt 2015-07-10 14:23:36 +0000
1162@@ -1,8 +1,14 @@
1163+include_directories(
1164+ ${CMAKE_CURRENT_SOURCE_DIR}
1165+ ${CMAKE_CURRENT_BINARY_DIR}
1166+)
1167+
1168 add_library(QtMultimedia-qml MODULE
1169 plugin.cpp
1170- audio.cpp
1171+ mediaplayer.cpp
1172+ videooutput.cpp
1173 )
1174
1175-qt5_use_modules(QtMultimedia-qml Qml)
1176+qt5_use_modules(QtMultimedia-qml Qml Quick)
1177
1178 add_unity8_mock(QtMultimedia 5.0 QtMultimedia TARGETS QtMultimedia-qml)
1179
1180=== added file 'tests/mocks/QtMultimedia/VideoSurface.qml'
1181--- tests/mocks/QtMultimedia/VideoSurface.qml 1970-01-01 00:00:00 +0000
1182+++ tests/mocks/QtMultimedia/VideoSurface.qml 2015-07-10 14:23:36 +0000
1183@@ -0,0 +1,111 @@
1184+/*
1185+ * Copyright (C) 2015 Canonical, Ltd.
1186+ *
1187+ * This program is free software; you can redistribute it and/or modify
1188+ * it under the terms of the GNU General Public License as published by
1189+ * the Free Software Foundation; version 3.
1190+ *
1191+ * This program is distributed in the hope that it will be useful,
1192+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1193+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1194+ * GNU General Public License for more details.
1195+ *
1196+ * You should have received a copy of the GNU General Public License
1197+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1198+ */
1199+
1200+import QtQuick 2.0
1201+import Ubuntu.Components 1.2
1202+import QtMultimedia 5.0
1203+
1204+Rectangle {
1205+ id: root
1206+ property int position: 0
1207+ property int playbackState: 0
1208+ property var resolution: { "width": 16, "height": 9 }
1209+
1210+ anchors.centerIn: parent
1211+ color: "black"
1212+
1213+ // scale to the parent window.
1214+ implicitWidth: parent ? aspectScale(Qt.size(resolution.width, resolution.height), Qt.size(parent.width, parent.height)).width : 160
1215+ implicitHeight: parent ? aspectScale(Qt.size(resolution.width, resolution.height), Qt.size(parent.width, parent.height)).height : 90
1216+
1217+ function aspectScale(fromSize, toSize) {
1218+ var rw = toSize.height * fromSize.width / fromSize.height;
1219+ var useHeight = (rw <= toSize.width);
1220+
1221+ if (useHeight) {
1222+ return Qt.size(rw, toSize.height);
1223+ } else {
1224+ return Qt.size(toSize.width,
1225+ toSize.width * (fromSize.height / fromSize.width));
1226+ }
1227+ }
1228+
1229+ GridView {
1230+ anchors.fill: parent
1231+ model: (parent.width / units.gu(5)) * ((parent.height / units.gu(5) + 1))
1232+ clip: true
1233+
1234+ cellHeight: units.gu(5)
1235+ cellWidth: units.gu(5)
1236+
1237+ delegate: Item {
1238+ width: units.gu(5)
1239+ height: units.gu(5)
1240+
1241+ Rectangle {
1242+ id: rect
1243+ color: "red"
1244+ anchors.fill: parent
1245+ anchors.margins: units.gu(1)
1246+
1247+ Connections {
1248+ target: root
1249+ onPositionChanged: {
1250+ rect.rotation = position/100
1251+ }
1252+ }
1253+ }
1254+ }
1255+ }
1256+
1257+ Column {
1258+ anchors {
1259+ left: parent.left;
1260+ right: parent.right
1261+ verticalCenter: parent.verticalCenter
1262+ }
1263+ height: units.gu(10)
1264+
1265+ Label {
1266+ anchors.horizontalCenter: parent.horizontalCenter
1267+ text: {
1268+ var pos = (position/1000).toFixed(0)
1269+
1270+ var m = Math.floor(pos/60);
1271+ var ss = pos % 60;
1272+ if (ss >= 10) {
1273+ return m + ":" + ss;
1274+ } else {
1275+ return m + ":0" + ss;
1276+ }
1277+ }
1278+ fontSize: "x-large"
1279+ }
1280+
1281+ Label {
1282+ anchors.horizontalCenter: parent.horizontalCenter
1283+ text: getPlaybackState(playbackState)
1284+ fontSize: "x-large"
1285+ }
1286+ }
1287+
1288+ function getPlaybackState(playbackState) {
1289+ if (playbackState === MediaPlayer.PlayingState) return "Playing";
1290+ else if (playbackState === MediaPlayer.PausedState) return "Paused";
1291+ else if (playbackState === MediaPlayer.StoppedState) return "Stopped";
1292+ return "";
1293+ }
1294+}
1295
1296=== renamed file 'tests/mocks/QtMultimedia/audio.cpp' => 'tests/mocks/QtMultimedia/mediaplayer.cpp'
1297--- tests/mocks/QtMultimedia/audio.cpp 2015-06-08 11:37:27 +0000
1298+++ tests/mocks/QtMultimedia/mediaplayer.cpp 2015-07-10 14:23:36 +0000
1299@@ -16,57 +16,123 @@
1300 * Author: Michael Zanetti <michael.zanetti@canonical.com>
1301 */
1302
1303-#include "audio.h"
1304-
1305-Audio::Audio(QObject* parent):
1306- QObject(parent),
1307- m_playbackState(StoppedState)
1308+#include "mediaplayer.h"
1309+
1310+#include <QDebug>
1311+#include <QSize>
1312+
1313+class MetaDataObject: public QObject
1314+{
1315+ Q_OBJECT
1316+ Q_PROPERTY(QVariant title READ title NOTIFY metaDataChanged)
1317+ Q_PROPERTY(QVariant resolution READ resolution NOTIFY metaDataChanged)
1318+public:
1319+ MetaDataObject(MediaPlayer* parent = nullptr)
1320+ : QObject(parent)
1321+ , m_source(parent)
1322+ {
1323+ }
1324+
1325+ QVariant title() const { return getProperty("title"); }
1326+ QVariant resolution() const { return getProperty("resolution", QSize(640, 640)); }
1327+
1328+ QVariant getProperty(const QString& property, QVariant defaultValue = QVariant()) const {
1329+ MediaDataSource* sourceData = MediaPlayerDataController::instance()->dataForSource(m_source->source());
1330+ QVariant metaData = sourceData->metaData();
1331+ return metaData.toMap().value(property, defaultValue);
1332+ }
1333+
1334+Q_SIGNALS:
1335+ void metaDataChanged();
1336+
1337+private:
1338+ MediaPlayer* m_source;
1339+};
1340+
1341+MediaPlayer::MediaPlayer(QObject* parent)
1342+ : QObject(parent)
1343+ , m_playbackState(StoppedState)
1344+ , m_status(NoMedia)
1345+ , m_metaData(new MetaDataObject(this))
1346 {
1347 qsrand(time(nullptr));
1348- m_timer.setInterval(1000);
1349+ m_timer.setInterval(100);
1350 connect(&m_timer, SIGNAL(timeout()), SLOT(timerEvent()));
1351+
1352+ connect(MediaPlayerDataController::instance(), &MediaPlayerDataController::sourceAboutToBeRemoved,
1353+ this, [this](const QUrl& source) {
1354+ MediaDataSource* dataSource = MediaPlayerDataController::instance()->dataForSource(source);
1355+ if (!dataSource) return;
1356+
1357+ disconnect(dataSource, &MediaDataSource::durationChanged, this, &MediaPlayer::durationChanged);
1358+ disconnect(dataSource, &MediaDataSource::seekableChanged, this, &MediaPlayer::seekableChanged);
1359+ disconnect(dataSource, &MediaDataSource::availabilityChanged, this, &MediaPlayer::availabilityChanged);
1360+ disconnect(dataSource, &MediaDataSource::metaDataChanged, m_metaData, &MetaDataObject::metaDataChanged);
1361+ });
1362+
1363+ connect(MediaPlayerDataController::instance(), &MediaPlayerDataController::sourceAdded,
1364+ this, [this](const QUrl& source) {
1365+ MediaDataSource* dataSource = MediaPlayerDataController::instance()->dataForSource(source);
1366+ if (!dataSource) return;
1367+
1368+ connect(dataSource, &MediaDataSource::durationChanged, this, &MediaPlayer::durationChanged);
1369+ connect(dataSource, &MediaDataSource::seekableChanged,this, &MediaPlayer::seekableChanged);
1370+ connect(dataSource, &MediaDataSource::availabilityChanged,this, &MediaPlayer::availabilityChanged);
1371+ connect(dataSource, &MediaDataSource::metaDataChanged, m_metaData, &MetaDataObject::metaDataChanged);
1372+ });
1373 }
1374
1375-QUrl Audio::source() const
1376+QUrl MediaPlayer::source() const
1377 {
1378 return m_source;
1379 }
1380
1381-void Audio::setSource(const QUrl &source)
1382+void MediaPlayer::setSource(const QUrl &source)
1383 {
1384 if (m_source != source) {
1385 m_source = source;
1386 Q_EMIT sourceChanged(source);
1387+ Q_EMIT durationChanged(duration());
1388+ Q_EMIT seekableChanged(isSeekable());
1389+ Q_EMIT availabilityChanged(availability());
1390
1391 m_position = 0;
1392 Q_EMIT positionChanged(m_position);
1393+ Q_EMIT m_metaData->metaDataChanged();
1394
1395- m_duration = (qrand() % 20000) + 10000;
1396- Q_EMIT durationChanged(m_duration);
1397+ m_status = availability()==Available ? Loaded : InvalidMedia;
1398+ Q_EMIT statusChanged();
1399 }
1400 }
1401
1402-Audio::PlaybackState Audio::playbackState() const
1403+MediaPlayer::PlaybackState MediaPlayer::playbackState() const
1404 {
1405 return m_playbackState;
1406 }
1407
1408-int Audio::position() const
1409+int MediaPlayer::position() const
1410 {
1411 return m_position;
1412 }
1413
1414-int Audio::duration() const
1415-{
1416- return m_duration;
1417-}
1418-
1419-QString Audio::errorString() const
1420+int MediaPlayer::duration() const
1421+{
1422+ MediaDataSource* dataSource = MediaPlayerDataController::instance()->dataForSource(source());
1423+ if (dataSource) return dataSource->duration();
1424+ return 0;
1425+}
1426+
1427+MediaPlayer::Error MediaPlayer::error() const
1428+{
1429+ return NoError;
1430+}
1431+
1432+QString MediaPlayer::errorString() const
1433 {
1434 return QString();
1435 }
1436
1437-void Audio::pause()
1438+void MediaPlayer::pause()
1439 {
1440 if (m_playbackState == PlayingState) {
1441 m_playbackState = PausedState;
1442@@ -75,7 +141,7 @@
1443 }
1444 }
1445
1446-void Audio::play()
1447+void MediaPlayer::play()
1448 {
1449 if (m_playbackState != PlayingState) {
1450 m_playbackState = PlayingState;
1451@@ -85,7 +151,7 @@
1452 }
1453 }
1454
1455-void Audio::stop()
1456+void MediaPlayer::stop()
1457 {
1458 if (m_playbackState != StoppedState) {
1459 m_playbackState = StoppedState;
1460@@ -96,22 +162,143 @@
1461 }
1462 }
1463
1464-void Audio::timerEvent()
1465-{
1466- if (m_position + 1000 < m_duration) {
1467- m_position += 1000;
1468+void MediaPlayer::seek(int position)
1469+{
1470+ if (status() != Loaded) return;
1471+ int newPosition = qMin(qMax(0, position), duration());
1472+ if (newPosition != m_position) {
1473+ m_position = newPosition;
1474+ Q_EMIT positionChanged(m_position);
1475+ }
1476+
1477+}
1478+
1479+void MediaPlayer::timerEvent()
1480+{
1481+ if (m_position + m_timer.interval() < duration()) {
1482+ m_position += m_timer.interval();
1483 Q_EMIT positionChanged(m_position);
1484 } else {
1485- stop();
1486+ pause();
1487 }
1488 }
1489
1490-Audio::AudioRole Audio::audioRole() const
1491+MediaPlayer::AudioRole MediaPlayer::audioRole() const
1492 {
1493- return Audio::multimedia;
1494+ return MediaPlayer::multimedia;
1495 }
1496
1497-void Audio::setAudioRole(Audio::AudioRole audioRole)
1498+void MediaPlayer::setAudioRole(MediaPlayer::AudioRole audioRole)
1499 {
1500 Q_UNUSED(audioRole);
1501 }
1502+
1503+bool MediaPlayer::isSeekable() const
1504+{
1505+ MediaDataSource* dataSource = MediaPlayerDataController::instance()->dataForSource(source());
1506+ if (dataSource) return dataSource->isSeekable();
1507+ return true;
1508+}
1509+
1510+MediaPlayer::Availability MediaPlayer::availability() const
1511+{
1512+ MediaDataSource* dataSource = MediaPlayerDataController::instance()->dataForSource(source());
1513+ if (dataSource) return dataSource->availability();
1514+ return Available;
1515+}
1516+
1517+QObject *MediaPlayer::metaData() const
1518+{
1519+ return m_metaData;
1520+}
1521+
1522+
1523+MediaDataSource::MediaDataSource(QObject *parent)
1524+ : QObject(parent)
1525+ , m_seekable(true)
1526+ , m_duration((qrand() % 20000) + 10000)
1527+ , m_availability(MediaPlayer::Available)
1528+{
1529+}
1530+
1531+MediaDataSource::~MediaDataSource()
1532+{
1533+ if (!m_source.isEmpty()) MediaPlayerDataController::instance()->unregisterDataSource(this);
1534+}
1535+
1536+void MediaDataSource::setSource(const QUrl &source)
1537+{
1538+ if (m_source != source) {
1539+ if (!m_source.isEmpty()) MediaPlayerDataController::instance()->unregisterDataSource(this);
1540+
1541+ m_source = source;
1542+
1543+ if (!m_source.isEmpty()) MediaPlayerDataController::instance()->registerDataSource(this);
1544+ Q_EMIT sourceChanged();
1545+ }
1546+}
1547+
1548+void MediaDataSource::setSeekable(bool seekable)
1549+{
1550+ if (m_seekable != seekable) {
1551+ m_seekable = seekable;
1552+ Q_EMIT seekableChanged(m_seekable);
1553+ }
1554+}
1555+
1556+void MediaDataSource::setDuration(int duration)
1557+{
1558+ if (m_duration != duration) {
1559+ m_duration = duration;
1560+ Q_EMIT durationChanged(m_duration);
1561+ }
1562+}
1563+
1564+void MediaDataSource::setAvailability(MediaPlayer::Availability availability)
1565+{
1566+ if (m_availability != availability) {
1567+ m_availability = availability;
1568+ Q_EMIT availabilityChanged(m_availability);
1569+ }
1570+}
1571+
1572+void MediaDataSource::setMetaData(const QVariant& metaData)
1573+{
1574+ if (m_metaData != metaData) {
1575+ m_metaData = metaData;
1576+ Q_EMIT metaDataChanged();
1577+ }
1578+}
1579+
1580+MediaPlayerDataController *MediaPlayerDataController::instance()
1581+{
1582+ static MediaPlayerDataController* instance = nullptr;
1583+ if (!instance) { instance = new MediaPlayerDataController(); }
1584+ return instance;
1585+}
1586+
1587+void MediaPlayerDataController::registerDataSource(MediaDataSource *dataSource)
1588+{
1589+ m_dataSources[dataSource->source()] = dataSource;
1590+ Q_EMIT sourceAdded(dataSource->source());
1591+}
1592+
1593+void MediaPlayerDataController::unregisterDataSource(MediaDataSource *dataSource)
1594+{
1595+ QList<QUrl> keys = m_dataSources.keys(dataSource);
1596+ Q_FOREACH(const QUrl& key, keys) {
1597+ Q_EMIT sourceAboutToBeRemoved(key);
1598+ m_dataSources.remove(key);
1599+ }
1600+}
1601+
1602+MediaDataSource *MediaPlayerDataController::dataForSource(const QUrl &source)
1603+{
1604+ if (!m_dataSources.contains(source)) {
1605+ static MediaDataSource defaultSource;
1606+ return &defaultSource;
1607+ }
1608+ return m_dataSources[source];
1609+}
1610+
1611+#include "mediaplayer.moc"
1612
1613=== renamed file 'tests/mocks/QtMultimedia/audio.h' => 'tests/mocks/QtMultimedia/mediaplayer.h'
1614--- tests/mocks/QtMultimedia/audio.h 2015-06-08 11:37:27 +0000
1615+++ tests/mocks/QtMultimedia/mediaplayer.h 2015-07-10 14:23:36 +0000
1616@@ -16,24 +16,39 @@
1617 * Authors: Michael Zanetti <michael.zanetti@canonical.com>
1618 */
1619
1620-#ifndef MOCK_AUDIO_H
1621-#define MOCK_AUDIO_H
1622+#ifndef MOCK_MEDIAPLAYER_H
1623+#define MOCK_MEDIAPLAYER_H
1624
1625 #include <QObject>
1626 #include <QUrl>
1627 #include <QTimer>
1628-
1629-class Audio: public QObject
1630+#include <QHash>
1631+#include <QVariant>
1632+
1633+class MetaDataObject;
1634+
1635+class MediaPlayer: public QObject
1636 {
1637 Q_OBJECT
1638- Q_ENUMS(PlaybackState)
1639- Q_ENUMS(AudioRole)
1640+
1641 Q_PROPERTY(QUrl source READ source WRITE setSource NOTIFY sourceChanged)
1642 Q_PROPERTY(PlaybackState playbackState READ playbackState NOTIFY playbackStateChanged)
1643 Q_PROPERTY(int position READ position NOTIFY positionChanged)
1644 Q_PROPERTY(int duration READ duration NOTIFY durationChanged)
1645+ Q_PROPERTY(Error error READ error NOTIFY errorChanged)
1646 Q_PROPERTY(QString errorString READ errorString NOTIFY errorStringChanged)
1647 Q_PROPERTY(AudioRole audioRole READ audioRole WRITE setAudioRole)
1648+
1649+ Q_PROPERTY(bool seekable READ isSeekable NOTIFY seekableChanged)
1650+ Q_PROPERTY(Availability availability READ availability NOTIFY availabilityChanged)
1651+ Q_PROPERTY(Status status READ status NOTIFY statusChanged)
1652+ Q_PROPERTY(QObject *metaData READ metaData CONSTANT)
1653+
1654+ Q_ENUMS(PlaybackState)
1655+ Q_ENUMS(AudioRole)
1656+ Q_ENUMS(Availability)
1657+ Q_ENUMS(Status)
1658+ Q_ENUMS(Error)
1659 public:
1660 enum PlaybackState {
1661 PlayingState,
1662@@ -48,7 +63,35 @@
1663 phone
1664 };
1665
1666- explicit Audio(QObject *parent = 0);
1667+ enum Availability {
1668+ Available,
1669+ Busy,
1670+ Unavailable,
1671+ ResourceMissing
1672+ };
1673+
1674+ enum Status {
1675+ UnknownStatus,
1676+ NoMedia,
1677+ Loading,
1678+ Loaded,
1679+ Stalled,
1680+ Buffering,
1681+ Buffered,
1682+ EndOfMedia,
1683+ InvalidMedia
1684+ };
1685+
1686+ enum Error {
1687+ NoError,
1688+ ResourceError,
1689+ FormatError,
1690+ NetworkError,
1691+ AccessDeniedError,
1692+ ServiceMissingError
1693+ };
1694+
1695+ explicit MediaPlayer(QObject *parent = 0);
1696
1697 QUrl source() const;
1698 void setSource(const QUrl &source);
1699@@ -59,22 +102,35 @@
1700
1701 int duration() const;
1702
1703+ Error error() const;
1704 QString errorString() const;
1705
1706 AudioRole audioRole() const;
1707 void setAudioRole(AudioRole audioRole);
1708
1709+ bool isSeekable() const;
1710+ MediaPlayer::Availability availability() const;
1711+ Status status() const { return m_status; }
1712+ QObject *metaData() const;
1713+
1714 public Q_SLOTS:
1715 void pause();
1716 void play();
1717 void stop();
1718+ void seek(int position);
1719
1720 Q_SIGNALS:
1721 void sourceChanged(const QUrl &source);
1722 void playbackStateChanged(PlaybackState playbackState);
1723 void positionChanged(int position);
1724 void durationChanged(int duration);
1725+ void seekableChanged(bool seekable);
1726+ void errorChanged(Error error);
1727 void errorStringChanged(const QString &errorString);
1728+ void availabilityChanged(Availability availability);
1729+ void statusChanged();
1730+
1731+ void error(Error error, const QString &errorString);
1732
1733 private Q_SLOTS:
1734 void timerEvent();
1735@@ -84,7 +140,72 @@
1736 PlaybackState m_playbackState;
1737 QTimer m_timer;
1738 int m_position;
1739+ Status m_status;
1740+ MetaDataObject *m_metaData;
1741+};
1742+
1743+class MediaDataSource : public QObject
1744+{
1745+ Q_OBJECT
1746+ Q_PROPERTY(QUrl source READ source WRITE setSource NOTIFY sourceChanged)
1747+ Q_PROPERTY(bool seekable READ isSeekable WRITE setSeekable NOTIFY seekableChanged)
1748+ Q_PROPERTY(int duration READ duration WRITE setDuration NOTIFY durationChanged)
1749+ Q_PROPERTY(MediaPlayer::Availability availability READ availability WRITE setAvailability NOTIFY availabilityChanged)
1750+ Q_PROPERTY(QVariant metaData READ metaData WRITE setMetaData NOTIFY metaDataChanged)
1751+
1752+public:
1753+ MediaDataSource(QObject* parent = 0);
1754+ ~MediaDataSource();
1755+
1756+ QUrl source() const { return m_source; }
1757+ void setSource(const QUrl& source);
1758+
1759+ bool isSeekable() const { return m_seekable; }
1760+ void setSeekable(bool seekable);
1761+
1762+ int duration() const { return m_duration; }
1763+ void setDuration(int duration);
1764+
1765+ MediaPlayer::Availability availability() const { return m_availability; }
1766+ void setAvailability(MediaPlayer::Availability availability);
1767+
1768+ QVariant metaData() const { return m_metaData; }
1769+ void setMetaData(const QVariant& metaData);
1770+
1771+Q_SIGNALS:
1772+ void sourceChanged();
1773+ void seekableChanged(bool seekable);
1774+ void durationChanged(int duration);
1775+ void availabilityChanged(MediaPlayer::Availability availability);
1776+ void metaDataChanged();
1777+
1778+private:
1779+ QUrl m_source;
1780+ bool m_seekable;
1781 int m_duration;
1782-};
1783-
1784-#endif
1785+ MediaPlayer::Availability m_availability;
1786+ QVariant m_metaData;
1787+};
1788+
1789+class MediaPlayerDataController : public QObject
1790+{
1791+ Q_OBJECT
1792+public:
1793+ MediaPlayerDataController() = default;
1794+
1795+ static MediaPlayerDataController *instance();
1796+
1797+ void registerDataSource(MediaDataSource* dataSource);
1798+ void unregisterDataSource(MediaDataSource* dataSource);
1799+
1800+ MediaDataSource* dataForSource(const QUrl& source);
1801+
1802+Q_SIGNALS:
1803+ void sourceAdded(const QUrl& source);
1804+ void sourceAboutToBeRemoved(const QUrl& source);
1805+
1806+public:
1807+ QHash<QUrl, MediaDataSource*> m_dataSources;
1808+};
1809+
1810+#endif // MOCK_MEDIAPLAYER_H
1811
1812=== modified file 'tests/mocks/QtMultimedia/plugin.cpp'
1813--- tests/mocks/QtMultimedia/plugin.cpp 2015-06-08 11:37:27 +0000
1814+++ tests/mocks/QtMultimedia/plugin.cpp 2015-07-10 14:23:36 +0000
1815@@ -17,13 +17,17 @@
1816 */
1817
1818 #include "plugin.h"
1819-#include "audio.h"
1820+#include "mediaplayer.h"
1821+#include "videooutput.h"
1822
1823 #include <QtQml/qqml.h>
1824
1825 void MockQtMultimediaPlugin::registerTypes(const char *uri)
1826 {
1827 Q_ASSERT(uri == QLatin1String("QtMultimedia"));
1828- qmlRegisterType<Audio>(uri, 5, 0, "Audio");
1829- qmlRegisterType<Audio>(uri, 5, 0, "MediaPlayer");
1830+ qmlRegisterType<MediaPlayer>(uri, 5, 0, "Audio");
1831+ qmlRegisterType<MediaPlayer>(uri, 5, 0, "MediaPlayer");
1832+ qmlRegisterType<VideoOutput>(uri, 5, 0, "VideoOutput");
1833+
1834+ qmlRegisterType<MediaDataSource>(uri, 5, 0, "MediaDataSource");
1835 }
1836
1837=== added file 'tests/mocks/QtMultimedia/videooutput.cpp'
1838--- tests/mocks/QtMultimedia/videooutput.cpp 1970-01-01 00:00:00 +0000
1839+++ tests/mocks/QtMultimedia/videooutput.cpp 2015-07-10 14:23:36 +0000
1840@@ -0,0 +1,127 @@
1841+/*
1842+ * Copyright (C) 2015 Canonical, Ltd.
1843+ *
1844+ * This program is free software; you can redistribute it and/or modify
1845+ * it under the terms of the GNU General Public License as published by
1846+ * the Free Software Foundation; version 3.
1847+ *
1848+ * This program is distributed in the hope that it will be useful,
1849+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1850+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1851+ * GNU General Public License for more details.
1852+ *
1853+ * You should have received a copy of the GNU General Public License
1854+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1855+ */
1856+
1857+#include "videooutput.h"
1858+#include "mediaplayer.h"
1859+
1860+#include <paths.h>
1861+
1862+#include <QQuickView>
1863+#include <QQmlComponent>
1864+#include <QGuiApplication>
1865+#include <QQmlProperty>
1866+#include <QQmlEngine>
1867+
1868+VideoOutput::VideoOutput(QQuickItem *parent)
1869+ : QQuickItem(parent)
1870+ , m_source(nullptr)
1871+ , m_qmlContentComponent(nullptr)
1872+ , m_qmlItem(nullptr)
1873+{
1874+}
1875+
1876+void VideoOutput::setSource(QObject *source)
1877+{
1878+ if (m_source != source) {
1879+ if (m_source) disconnect(m_source, 0, this, 0);
1880+
1881+ m_source = source;
1882+ Q_EMIT sourceChanged();
1883+
1884+ MediaPlayer* mediaPlayer = qobject_cast<MediaPlayer*>(source);
1885+ if (mediaPlayer) {
1886+ connect(mediaPlayer, &MediaPlayer::positionChanged, this, &VideoOutput::updateProperties);
1887+ connect(mediaPlayer, &MediaPlayer::playbackStateChanged, this, &VideoOutput::updateProperties);
1888+ connect(mediaPlayer->metaData(), SIGNAL(metaDataChanged()), this, SLOT(updateProperties()));
1889+ }
1890+ }
1891+}
1892+
1893+void VideoOutput::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value)
1894+{
1895+ if (change == QQuickItem::ItemSceneChange && !m_qmlContentComponent) {
1896+ QWindowList list = QGuiApplication::topLevelWindows();
1897+ if (list.isEmpty()) return;
1898+
1899+ // The assumptions I make here really should hold.
1900+ QQuickView *quickView =
1901+ qobject_cast<QQuickView*>(list[0]);
1902+
1903+ m_qmlContentComponent = new QQmlComponent(quickView->engine(),
1904+ QString("%1/QtMultimedia/VideoSurface.qml").arg(mockPluginsDir()));
1905+
1906+ switch (m_qmlContentComponent->status()) {
1907+ case QQmlComponent::Ready:
1908+ createQmlContentItem();
1909+ break;
1910+ case QQmlComponent::Loading:
1911+ connect(m_qmlContentComponent, &QQmlComponent::statusChanged,
1912+ this, &VideoOutput::onComponentStatusChanged);
1913+ break;
1914+ case QQmlComponent::Error:
1915+ printComponentErrors();
1916+ qFatal("VideoOutput: failed to create content component.");
1917+ break;
1918+ default:
1919+ qFatal("VideoOutput: Unhandled component status");
1920+ }
1921+ }
1922+ QQuickItem::itemChange(change, value);
1923+}
1924+
1925+void VideoOutput::printComponentErrors()
1926+{
1927+ QList<QQmlError> errors = m_qmlContentComponent->errors();
1928+ for (int i = 0; i < errors.count(); ++i) {
1929+ qDebug() << errors[i];
1930+ }
1931+}
1932+
1933+void VideoOutput::onComponentStatusChanged(QQmlComponent::Status status)
1934+{
1935+ if (status == QQmlComponent::Ready) {
1936+ createQmlContentItem();
1937+ }
1938+}
1939+
1940+void VideoOutput::createQmlContentItem()
1941+{
1942+ m_qmlItem = qobject_cast<QQuickItem*>(m_qmlContentComponent->create());
1943+ m_qmlItem->setParentItem(this);
1944+
1945+ updateProperties();
1946+}
1947+
1948+void VideoOutput:: updateProperties()
1949+{
1950+ if (!m_qmlItem) return;
1951+
1952+ MediaPlayer* mediaPlayer = qobject_cast<MediaPlayer*>(m_source);
1953+ if (!mediaPlayer) return;
1954+
1955+ QQmlProperty playbackStateProperty(m_qmlItem, "playbackState");
1956+ playbackStateProperty.write(QVariant::fromValue<int>(mediaPlayer->playbackState()));
1957+
1958+ QQmlProperty positionProperty(m_qmlItem, "position");
1959+ positionProperty.write(QVariant::fromValue(mediaPlayer->position()));
1960+
1961+ QQmlProperty propResSource(mediaPlayer->metaData(), "resolution");
1962+ QQmlProperty propResDest(m_qmlItem, "resolution");
1963+
1964+ if (propResSource.isValid() && propResDest.isValid()) {
1965+ propResDest.write(propResSource.read());
1966+ }
1967+}
1968
1969=== added file 'tests/mocks/QtMultimedia/videooutput.h'
1970--- tests/mocks/QtMultimedia/videooutput.h 1970-01-01 00:00:00 +0000
1971+++ tests/mocks/QtMultimedia/videooutput.h 2015-07-10 14:23:36 +0000
1972@@ -0,0 +1,52 @@
1973+/*
1974+ * Copyright (C) 2015 Canonical, Ltd.
1975+ *
1976+ * This program is free software; you can redistribute it and/or modify
1977+ * it under the terms of the GNU General Public License as published by
1978+ * the Free Software Foundation; version 3.
1979+ *
1980+ * This program is distributed in the hope that it will be useful,
1981+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1982+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1983+ * GNU General Public License for more details.
1984+ *
1985+ * You should have received a copy of the GNU General Public License
1986+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1987+ */
1988+
1989+#ifndef VIDEOOUTPUT_H
1990+#define VIDEOOUTPUT_H
1991+
1992+#include <QQuickItem>
1993+#include <QPointer>
1994+class QQmlComponent;
1995+
1996+class VideoOutput : public QQuickItem
1997+{
1998+ Q_OBJECT
1999+ Q_PROPERTY(QObject* source READ source WRITE setSource NOTIFY sourceChanged)
2000+public:
2001+ explicit VideoOutput(QQuickItem *parent = 0);
2002+
2003+ QObject *source() const { return m_source.data(); }
2004+ void setSource(QObject *source);
2005+
2006+ void itemChange(ItemChange change, const ItemChangeData & value);
2007+
2008+Q_SIGNALS:
2009+ void sourceChanged();
2010+
2011+protected Q_SLOTS:
2012+ void onComponentStatusChanged(QQmlComponent::Status status);
2013+ void updateProperties();
2014+
2015+private:
2016+ void createQmlContentItem();
2017+ void printComponentErrors();
2018+
2019+ QPointer<QObject> m_source;
2020+ QQmlComponent* m_qmlContentComponent;
2021+ QQuickItem* m_qmlItem;
2022+};
2023+
2024+#endif // VIDEOOUTPUT_H
2025
2026=== modified file 'tests/qmltests/CMakeLists.txt'
2027--- tests/qmltests/CMakeLists.txt 2015-06-18 19:23:08 +0000
2028+++ tests/qmltests/CMakeLists.txt 2015-07-10 14:23:36 +0000
2029@@ -10,6 +10,7 @@
2030 add_unity8_qmltest(Components DraggingArea)
2031 add_unity8_qmltest(Components LazyImage)
2032 add_unity8_qmltest(Components Lockscreen)
2033+add_unity8_qmltest(Components MediaServices)
2034 add_unity8_qmlunittest(Components PhysicalKeysMapper)
2035 add_unity8_qmltest(Components Rating)
2036 add_unity8_qmltest(Components ResponsiveGridView)
2037@@ -30,6 +31,7 @@
2038 add_unity8_qmltest(Dash/Previews PreviewExpandable)
2039 add_unity8_qmltest(Dash/Previews PreviewHeader)
2040 add_unity8_qmltest(Dash/Previews PreviewImageGallery)
2041+add_unity8_qmltest(Dash/Previews PreviewInlineVideo)
2042 add_unity8_qmltest(Dash/Previews PreviewPayments)
2043 add_unity8_qmltest(Dash/Previews PreviewProgress)
2044 add_unity8_qmltest(Dash/Previews PreviewRatingDisplay)
2045
2046=== added file 'tests/qmltests/Components/tst_MediaServices.qml'
2047--- tests/qmltests/Components/tst_MediaServices.qml 1970-01-01 00:00:00 +0000
2048+++ tests/qmltests/Components/tst_MediaServices.qml 2015-07-10 14:23:36 +0000
2049@@ -0,0 +1,228 @@
2050+/*
2051+ * Copyright 2015 Canonical Ltd.
2052+ *
2053+ * This program is free software; you can redistribute it and/or modify
2054+ * it under the terms of the GNU General Public License as published by
2055+ * the Free Software Foundation; version 3.
2056+ *
2057+ * This program is distributed in the hope that it will be useful,
2058+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2059+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2060+ * GNU General Public License for more details.
2061+ *
2062+ * You should have received a copy of the GNU General Public License
2063+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2064+ */
2065+
2066+import QtQuick 2.0
2067+import QtTest 1.0
2068+import "../../../qml/Components/MediaServices"
2069+import Unity.Test 0.1 as UT
2070+import Ubuntu.Components 1.2
2071+import QtMultimedia 5.0
2072+
2073+Rectangle {
2074+ id: root
2075+ width: units.gu(70)
2076+ height: units.gu(80)
2077+ color: "lightgrey"
2078+
2079+ property var sourceData: {
2080+ "source": "file:///home/nick/Videos/test-mpeg.ogv",
2081+ "screenshot": Qt.resolvedUrl("../Dash/artwork/avatar.png")
2082+ }
2083+
2084+ property var sourceData2: {
2085+ "source": "file:///home/nick/Videos/test-mpeg2.ogv",
2086+ "screenshot": Qt.resolvedUrl("../Dash/artwork/checkers.png")
2087+ }
2088+
2089+ MediaDataSource {
2090+ source: root.sourceData["source"]
2091+ duration: 60000
2092+ metaData: {
2093+ "title" : "TEST MPEG",
2094+ "resolution" : { "width": 160, "height": 90 }
2095+ }
2096+ }
2097+
2098+ MediaDataSource {
2099+ source: root.sourceData2["source"]
2100+ duration: 30000
2101+ metaData: {
2102+ "title" : "TEST MPEG",
2103+ "resolution" : { "width": 90, "height": 240 }
2104+ }
2105+ }
2106+
2107+ Item {
2108+ anchors.fill: parent
2109+
2110+ Rectangle {
2111+ anchors {
2112+ top: parent.top
2113+ bottom: parent.bottom
2114+ left: parent.left
2115+ right: controls.left
2116+ }
2117+ color: "darkblue"
2118+
2119+ MediaServices {
2120+ id: services
2121+ fullscreen: false
2122+ width: parent.width
2123+ sourceData: root.sourceData
2124+ context: "video"
2125+
2126+ rootItem: parent
2127+ maximumEmbeddedHeight: undefined
2128+ }
2129+ }
2130+
2131+ Rectangle {
2132+ id: controls
2133+ color: "darkgrey"
2134+ anchors {
2135+ top: parent.top
2136+ bottom: parent.bottom
2137+ right: parent.right
2138+ }
2139+ width: units.gu(30)
2140+
2141+ Column {
2142+ anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
2143+
2144+ Button {
2145+ text: "Fullscreen"
2146+ onClicked: services.fullscreen = !services.fullscreen
2147+ }
2148+ UT.MouseTouchEmulationCheckbox {}
2149+ }
2150+ }
2151+ }
2152+
2153+ SignalSpy {
2154+ id: serviceCloseSpy
2155+ target: services
2156+ signalName: "close"
2157+ }
2158+
2159+ UT.UnityTestCase {
2160+ name: "VideoMediaServices"
2161+ when: windowShown
2162+
2163+ property int oldControlTimerInterval: 0;
2164+
2165+ function init() {
2166+ services.sourceData = root.sourceData
2167+ services.context = "video";
2168+
2169+ var controlHideTimer = findInvisibleChild(services, "controlHideTimer");
2170+ verify(controlHideTimer !== null);
2171+ oldControlTimerInterval = controlHideTimer.interval;
2172+ }
2173+
2174+ function cleanup() {
2175+ serviceCloseSpy.clear();
2176+ services.context = "";
2177+ services.fullscreen = false;
2178+ services.maximumEmbeddedHeight = undefined;
2179+ tryCompareFunction(function() { return services.header ? services.header.visible : false }, false);
2180+ tryCompareFunction(function() { return services.footer ? services.footer.visible : false }, false);
2181+
2182+ var controlHideTimer = findInvisibleChild(services, "controlHideTimer");
2183+ controlHideTimer.interval = oldControlTimerInterval;
2184+ }
2185+
2186+ function test_videoWidget() {
2187+ var videoPlayer = findChild(services, "videoPlayer");
2188+ verify(videoPlayer !== null);
2189+
2190+ var videoControls = findChild(services, "videoControls");
2191+ verify(videoControls !== null);
2192+ }
2193+
2194+ function test_tapVideoPlayerToPlayAndPause() {
2195+ var videoPlayer = findChild(services, "videoPlayer");
2196+ verify(videoPlayer !== null)
2197+ tap(videoPlayer, videoPlayer.width/2, videoPlayer.height/2);
2198+
2199+ var mediaPlayer = findInvisibleChild(services, "mediaPlayer");
2200+ verify(mediaPlayer !== null);
2201+ compare(mediaPlayer.playbackState, MediaPlayer.PlayingState, "Media player should be playing");
2202+
2203+ tap(videoPlayer, videoPlayer.width/2, videoPlayer.height/2);
2204+ compare(mediaPlayer.playbackState, MediaPlayer.PausedState, "Media player should be playing");
2205+ }
2206+
2207+ function test_fullscreenSwitch() {
2208+ var mediaPlayer = findInvisibleChild(services, "mediaPlayer");
2209+ verify(mediaPlayer !== null);
2210+ mediaPlayer.play();
2211+ wait(UbuntuAnimation.BriskDuration); // animation
2212+
2213+ var button = findChild(services, "viewActionButton");
2214+ verify(button !== null);
2215+ tap(button);
2216+
2217+ compare(services.fullscreen, true, "Should have switched to fullscreen mode.");
2218+ }
2219+
2220+ function test_HeaderVisibleOnlyWhenFullscreen() {
2221+ services.fullscreen = false;
2222+ compare(services.header, null, "Header should be null when not fullscreen");
2223+ services.fullscreen = true;
2224+ tryCompareFunction(function() { return services.header !== null }, true);
2225+ }
2226+
2227+ function test_ControlsShowAndHideWhenPlayed() {
2228+ services.fullscreen = true;
2229+ var controlHideTimer = findInvisibleChild(services, "controlHideTimer");
2230+ controlHideTimer.interval = 100;
2231+
2232+ var mediaPlayer = findInvisibleChild(services, "mediaPlayer");
2233+ mediaPlayer.play();
2234+
2235+ tryCompare(services.header, "visible", true);
2236+ tryCompare(services.footer, "visible", true);
2237+
2238+ tryCompare(services.header, "visible", false);
2239+ tryCompare(services.footer, "visible", false);
2240+ }
2241+
2242+ function test_ControlsDontTimeOutWhenPaused() {
2243+ services.fullscreen = true;
2244+ var controlHideTimer = findInvisibleChild(services, "controlHideTimer");
2245+ controlHideTimer.interval = 100;
2246+
2247+ var mediaPlayer = findInvisibleChild(services, "mediaPlayer");
2248+ mediaPlayer.play();
2249+ mediaPlayer.pause();
2250+ wait(300);
2251+
2252+ compare(services.header.visible, true, "Header should still be visible");
2253+ compare(services.footer.visible, true, "Footer should still be visible");
2254+ }
2255+
2256+ function test_close() {
2257+ services.fullscreen = true;
2258+ var mediaPlayer = findInvisibleChild(services, "mediaPlayer");
2259+ mediaPlayer.play();
2260+ mediaPlayer.pause();
2261+ wait(UbuntuAnimation.BriskDuration); // animation
2262+
2263+ var navigationButton = findChild(services, "navigationButton");
2264+ verify(navigationButton !== null);
2265+
2266+ tap(navigationButton);
2267+ compare(serviceCloseSpy.count, 1, "close was not called");
2268+ }
2269+
2270+ function test_maximumVideoSize() {
2271+ services.maximumEmbeddedHeight = root.height / 2
2272+ services.sourceData = root.sourceData2
2273+ verify(services.content !== null);
2274+ tryCompare(services.content, "height", root.height/2);
2275+ }
2276+ }
2277+}
2278
2279=== added file 'tests/qmltests/Dash/Previews/tst_PreviewInlineVideo.qml'
2280--- tests/qmltests/Dash/Previews/tst_PreviewInlineVideo.qml 1970-01-01 00:00:00 +0000
2281+++ tests/qmltests/Dash/Previews/tst_PreviewInlineVideo.qml 2015-07-10 14:23:36 +0000
2282@@ -0,0 +1,106 @@
2283+/*
2284+ * Copyright 2015 Canonical Ltd.
2285+ *
2286+ * This program is free software; you can redistribute it and/or modify
2287+ * it under the terms of the GNU General Public License as published by
2288+ * the Free Software Foundation; version 3.
2289+ *
2290+ * This program is distributed in the hope that it will be useful,
2291+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2292+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2293+ * GNU General Public License for more details.
2294+ *
2295+ * You should have received a copy of the GNU General Public License
2296+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2297+ */
2298+
2299+import QtQuick 2.0
2300+import QtTest 1.0
2301+import "../../../../qml/Dash/Previews"
2302+import Unity.Test 0.1 as UT
2303+import Ubuntu.Components 1.2
2304+import QtMultimedia 5.0
2305+
2306+Rectangle {
2307+ width: units.gu(70)
2308+ height: units.gu(80)
2309+ color: "lightgrey"
2310+
2311+ property var widgetData0: {
2312+ "source": "file:///test-video1",
2313+ "screenshot": Qt.resolvedUrl("../artwork/avatar.png")
2314+ }
2315+
2316+ property var widgetData1: {
2317+ "source": "file:///test-video2",
2318+ "screenshot": "file:///test-video2-screenshot"
2319+ }
2320+
2321+ MediaDataSource {
2322+ source: "file:///test-video1"
2323+ duration: 6000000
2324+ metaData: {
2325+ "title" : "TEST MPEG",
2326+ "resolution" : { "width": 1920, "height": 1080 }
2327+ }
2328+ }
2329+
2330+ Item {
2331+ anchors.fill: parent
2332+
2333+ Item {
2334+ id: inner
2335+ anchors {
2336+ top: parent.top
2337+ bottom: parent.bottom
2338+ left: parent.left
2339+ right: controls.left
2340+ }
2341+
2342+ Item {
2343+ anchors.fill: parent
2344+ anchors.margins: units.gu(2)
2345+
2346+ PreviewInlineVideo {
2347+ id: videoPlayback
2348+ width: parent.width
2349+ widgetData: widgetData0
2350+
2351+ rootItem: inner
2352+ }
2353+ }
2354+
2355+ }
2356+
2357+ Rectangle {
2358+ id: controls
2359+ color: "darkgrey"
2360+ anchors {
2361+ top: parent.top
2362+ bottom: parent.bottom
2363+ right: parent.right
2364+ }
2365+ width: units.gu(30)
2366+
2367+ Column {
2368+ anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
2369+
2370+ UT.MouseTouchEmulationCheckbox {}
2371+ }
2372+ }
2373+ }
2374+
2375+ UT.UnityTestCase {
2376+ name: "PreviewInlineVideoTest"
2377+ when: windowShown
2378+
2379+ function test_loadScreenshot() {
2380+ var screenshot = findChild(videoPlayback, "screenshot");
2381+ verify(screenshot !== null);
2382+
2383+ videoPlayback.widgetData = widgetData1;
2384+ var screenshotSource = screenshot.source
2385+ compare(screenshotSource.toString(), "file:///test-video2-screenshot");
2386+ }
2387+ }
2388+}
2389
2390=== modified file 'tests/qmltests/Dash/Previews/tst_PreviewWidgetFactory.qml'
2391--- tests/qmltests/Dash/Previews/tst_PreviewWidgetFactory.qml 2014-08-26 19:21:31 +0000
2392+++ tests/qmltests/Dash/Previews/tst_PreviewWidgetFactory.qml 2015-07-10 14:23:36 +0000
2393@@ -75,24 +75,25 @@
2394
2395 function test_mapping_data() {
2396 return [
2397- { tag: "Actions", type: "actions", source: "PreviewActions.qml" },
2398- { tag: "Audio", type: "audio", source: "PreviewAudioPlayback.qml" },
2399- { tag: "Expandable", type: "expandable", source: "PreviewExpandable.qml" },
2400- { tag: "Gallery", type: "gallery", source: "PreviewImageGallery.qml" },
2401- { tag: "Header", type: "header", source: "PreviewHeader.qml" },
2402- { tag: "Image", type: "image", source: "PreviewZoomableImage.qml" },
2403- { tag: "Progress", type: "progress", source: "PreviewProgress.qml" },
2404- { tag: "Rating Input", type: "rating-input", source: "PreviewRatingInput.qml" },
2405- { tag: "Rating Display", type: "reviews", source: "PreviewRatingDisplay.qml" },
2406- { tag: "Table", type: "table", source: "PreviewTable.qml" },
2407- { tag: "Text", type: "text", source: "PreviewTextSummary.qml" },
2408- { tag: "Video", type: "video", source: "PreviewVideoPlayback.qml" },
2409+ { tag: "Actions", data: { type: "actions" }, source: "PreviewActions.qml" },
2410+ { tag: "Audio", data: { type: "audio" }, source: "PreviewAudioPlayback.qml" },
2411+ { tag: "Expandable", data: { type: "expandable" }, source: "PreviewExpandable.qml" },
2412+ { tag: "Gallery", data: { type: "gallery" }, source: "PreviewImageGallery.qml" },
2413+ { tag: "Header", data: { type: "header" }, source: "PreviewHeader.qml" },
2414+ { tag: "Image", data: { type: "image" }, source: "PreviewZoomableImage.qml" },
2415+ { tag: "Progress", data: { type: "progress" }, source: "PreviewProgress.qml" },
2416+ { tag: "Rating Input", data: { type: "rating-input" }, source: "PreviewRatingInput.qml" },
2417+ { tag: "Rating Display", data: { type: "reviews" }, source: "PreviewRatingDisplay.qml" },
2418+ { tag: "Table", data: { type: "table" }, source: "PreviewTable.qml" },
2419+ { tag: "Text", data: { type: "text" }, source: "PreviewTextSummary.qml" },
2420+ { tag: "Video", data: { type: "video" }, source: "PreviewInlineVideo.qml" },
2421+ { tag: "Video", data: { type: "video", source: "http://demo" }, source: "PreviewVideoPlayback.qml" },
2422 ];
2423 }
2424
2425 function test_mapping(data) {
2426- factory.widgetData = { type: data.type };
2427- factory.widgetType = data.type;
2428+ factory.widgetData = data.data;
2429+ factory.widgetType = data.data.type;
2430
2431 verify((String(factory.source)).indexOf(data.source) != -1);
2432 }

Subscribers

People subscribed via source and target branches