@@ -0,0 +1,332 @@
1+/*
2+ * Copyright (C) 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 QtMultimedia 5.0
19+import Ubuntu.Components 1.3
20+
21+FocusScope {
22+ id: root
23+ property var sourceData
24+ property string context: ""
25+ property bool fullscreen: false
26+ property list<Action> actions
27+
28+ property alias header: headerContent.item
29+ property alias content: contentLoader.item
30+ property alias footer: footerContent.item
31+
32+ implicitHeight: {
33+ if (fullscreen && parent) {
34+ return parent.height;
35+ }
36+ console.log(contentLoader.height, fullscreen, parent)
37+ return contentLoader.height
38+ }
39+ height: implicitHeight
40+
41+ OrientationHelper {
42+ id: orientationHelper
43+ automaticOrientation: false
44+
45+ states: [
46+ State {
47+ name: "portrait"
48+ when: !rotationAction.checked
49+ },
50+ State {
51+ name: "landscape"
52+ when: rotationAction.checked
53+ PropertyChanges {
54+ target: orientationHelper
55+ orientationAngle: 90
56+ }
57+ }
58+ ]
59+
60+ Loader {
61+ id: contentLoader
62+
63+ sourceComponent: {
64+ switch (context) {
65+ case "video":
66+ return videoComponent;
67+ }
68+ return undefined;
69+ }
70+
71+ Component {
72+ id: videoComponent
73+ VideoPlayer {
74+ id: player
75+ width: orientationHelper.width
76+ height: fullscreen ? orientationHelper.height : implicitHeight
77+ fixedHeight: fullscreen
78+ orientation: orientationHelper.state == "portrait" ? Qt.PortraitOrientation : Qt.LandscapeOrientation
79+
80+ screenshot: {
81+ var screenshot = root.sourceData["screenshot"];
82+ if (screenshot) return screenshot;
83+
84+ var source = root.sourceData["source"];
85+ if (source) {
86+ if (source.toString().indexOf("file://") === 0) {
87+ return "image://thumbnailer/" + source.toString().substr(7);
88+ }
89+ }
90+ return "";
91+ }
92+
93+ mediaPlayer: footer ? footer.mediaPlayer : null
94+
95+ onClicked: {
96+ if (mediaPlayer.availability !== MediaPlayer.Available) return;
97+
98+ if (mediaPlayer.playbackState === MediaPlayer.StoppedState) {
99+ mediaPlayer.play();
100+ } else if (controlHideTimer.running) {
101+ if (mediaPlayer.playbackState === MediaPlayer.PlayingState) {
102+ mediaPlayer.pause();
103+ } else {
104+ mediaPlayer.play();
105+ }
106+ } else {
107+ controlHideTimer.restart();
108+ }
109+ }
110+ onPositionChanged: controlHideTimer.restart();
111+ }
112+ }
113+ }
114+
115+ Loader {
116+ id: headerContent
117+ anchors {
118+ top: parent.top
119+ left: parent.left
120+ right: parent.right
121+ topMargin: -units.gu(6)
122+ }
123+ height: units.gu(6)
124+ visible: false
125+
126+ // eater
127+ MouseArea {
128+ anchors.fill: parent
129+ }
130+
131+ sourceComponent: root.fullscreen ? headerComponent : undefined
132+
133+ Component {
134+ id: headerComponent
135+
136+ MediaServicesHeader {
137+ onGoPrevious: {
138+ rotationAction.checked = false;
139+ fullscreen = false;
140+ }
141+
142+ component: {
143+ switch (context) {
144+ case "video":
145+ break;
146+ }
147+ return undefined;
148+ }
149+ }
150+ }
151+
152+ // If we interact with the bar, reset the hide timer.
153+ MouseArea {
154+ z: 1
155+ anchors.fill: parent
156+ onPressed: {
157+ mouse.accepted = false
158+ if (controlHideTimer.running) controlHideTimer.restart()
159+ }
160+ }
161+ }
162+
163+ Loader {
164+ id: footerContent
165+ anchors {
166+ left: parent.left
167+ right: parent.right
168+ bottom: parent.bottom
169+ bottomMargin: -units.gu(7)
170+ }
171+ height: units.gu(7)
172+ visible: false
173+
174+ sourceComponent: {
175+ switch (context) {
176+ case "video":
177+ return videoControlsComponent;
178+ }
179+ return undefined;
180+ }
181+ // eater
182+ MouseArea {
183+ anchors.fill: parent
184+ }
185+
186+ Component {
187+ id: videoControlsComponent
188+ VideoPlayerControls {
189+ id: controls
190+
191+ viewAction: rotationAction.enabled ? rotationAction : fullscreenAction
192+ userActions: root.actions
193+
194+ mediaPlayer.source: {
195+ var x = root.sourceData["source"];
196+ if (x.toString().indexOf("video://") === 0) {
197+ return x.toString().substr(6);
198+ }
199+ return x;
200+ }
201+ mediaPlayer.onPlaybackStateChanged: {
202+ controlHideTimer.restart();
203+ }
204+
205+ Binding {
206+ target: controlStates
207+ property: "forceControlsShown"
208+
209+ value: (fullscreen && mediaPlayer.playbackState === MediaPlayer.StoppedState) ||
210+ mediaPlayer.playbackState === MediaPlayer.PausedState ||
211+ interacting
212+ }
213+
214+ onInteractingChanged: {
215+ controlHideTimer.restart();
216+ }
217+
218+ Binding {
219+ target: header
220+ property: "title"
221+ value: controls.mediaPlayer.metaData.title
222+ when: header != null
223+ }
224+ }
225+ }
226+
227+ // If we interact with the bar, reset the hide timer.
228+ MouseArea {
229+ z: 1
230+ anchors.fill: parent
231+ onPressed: {
232+ mouse.accepted = false
233+ if (controlHideTimer.running) controlHideTimer.restart()
234+ }
235+ }
236+ }
237+ }
238+
239+ StateGroup {
240+ id: controlStates
241+ property bool controlTimerActive: false
242+ property bool forceControlsShown: false
243+
244+ states: [
245+ State {
246+ name: "controlsShown"
247+ when: controlStates.forceControlsShown || controlStates.controlTimerActive
248+ PropertyChanges {
249+ target: footerContent
250+ anchors.bottomMargin: 0
251+ visible: true
252+ }
253+ PropertyChanges {
254+ target: headerContent
255+ anchors.topMargin: 0
256+ visible: true
257+ }
258+ }
259+ ]
260+
261+ transitions: [
262+ Transition {
263+ to: "controlsShown"
264+ SequentialAnimation {
265+ PropertyAction { target: root; property: "clip"; value: true }
266+ PropertyAction { property: "visible" }
267+ NumberAnimation {
268+ properties: "anchors.bottomMargin,anchors.topMargin"
269+ duration: UbuntuAnimation.FastDuration
270+ }
271+ PropertyAction { target: root; property: "clip"; value: false }
272+ }
273+ },
274+ Transition {
275+ from: "controlsShown"
276+ SequentialAnimation {
277+ PropertyAction { target: root; property: "clip"; value: true }
278+ NumberAnimation {
279+ properties: "anchors.bottomMargin,anchors.topMargin"
280+ duration: UbuntuAnimation.FastDuration
281+ }
282+ PropertyAction { property: "visible" }
283+ PropertyAction { target: root; property: "clip"; value: false }
284+ }
285+ }
286+ ]
287+ }
288+
289+ Timer {
290+ id: controlHideTimer
291+ interval: 4000
292+ running: false
293+
294+ onRunningChanged: {
295+ if (running) {
296+ controlStates.controlTimerActive = true;
297+ }
298+ }
299+ onTriggered: controlStates.controlTimerActive = false;
300+ }
301+
302+ Action {
303+ id: fullscreenAction
304+ 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+ }
315+ }
316+
317+ Action {
318+ id: rotationAction
319+ enabled: root.fullscreen === true
320+ iconName: "orientation-lock"
321+
322+ 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+ }
331+ }
332+}