29cfea400cba123fba47809268494a817415f762
[harmattan/cameraplus] / declarative / videoplayer.cpp
1 /*!
2  * This file is part of CameraPlus.
3  *
4  * Copyright (C) 2012-2013 Mohammed Sameer <msameer@foolab.org>
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
19  */
20
21 #include "videoplayer.h"
22 #include <QDeclarativeInfo>
23 #include "qtcamgraphicsviewfinder.h"
24 #include "cameraconfig.h"
25 #include <QTimer>
26
27 VideoPlayer::VideoPlayer(QDeclarativeItem *parent) :
28   QDeclarativeItem(parent),
29   m_config(0),
30   m_vf(0),
31   m_bin(0),
32   m_state(VideoPlayer::StateStopped),
33   m_timer(new QTimer(this)),
34   m_pos(0) {
35
36   m_timer->setSingleShot(false);
37   m_timer->setInterval(500);
38   QObject::connect(m_timer, SIGNAL(timeout()), this, SIGNAL(positionChanged()));
39 }
40
41 VideoPlayer::~VideoPlayer() {
42   stop();
43
44   if (m_bin) {
45     gst_object_unref(m_bin);
46     m_bin = 0;
47   }
48 }
49
50 void VideoPlayer::componentComplete() {
51
52 }
53
54 void VideoPlayer::classBegin() {
55   m_bin = gst_element_factory_make ("playbin2", "VideoPlayerBin");
56   if (!m_bin) {
57     qmlInfo(this) << "Failed to create playbin2";
58     return;
59   }
60
61   g_object_set (m_bin, "flags", 99, NULL);
62
63   GstElement *elem = gst_element_factory_make("pulsesink", "VideoPlayerPulseSink");
64   if (!elem) {
65     qmlInfo(this) << "Failed to create pulsesink";
66   }
67   else {
68     // TODO: properties on sink
69     g_object_set (m_bin, "audio-sink", elem, NULL);
70   }
71
72   GstBus *bus = gst_element_get_bus(m_bin);
73   gst_bus_add_watch(bus, bus_call, this);
74   gst_object_unref(bus);
75 }
76
77 QUrl VideoPlayer::source() const {
78   return m_url;
79 }
80
81 void VideoPlayer::setSource(const QUrl& source) {
82   if (m_url != source) {
83     m_url = source;
84     emit sourceChanged();
85   }
86 }
87
88 CameraConfig *VideoPlayer::cameraConfig() const {
89   return m_config;  
90 }
91
92 void VideoPlayer::setCameraConfig(CameraConfig *config) {
93   if (m_config && m_config != config) {
94     qmlInfo(this) << "Cannot reset CameraConfig";
95     return;
96   }
97
98   if (!config) {
99     qmlInfo(this) << "CameraConfig cannot be empty";
100     return;
101   }
102
103   if (m_config != config) {
104     m_config = config;
105     // TODO: We need fence sync here.
106     m_vf = new QtCamGraphicsViewfinder(m_config->config(), this);
107     m_vf->resize(QSizeF(width(), height()));
108     emit cameraConfigChanged();
109   }
110
111   if (m_bin) {
112     g_object_set(m_bin, "video-sink", m_vf->sinkElement(), NULL);
113   }
114 }
115
116 qint64 VideoPlayer::duration() const {
117   if (!m_bin) {
118     return 0;
119   }
120
121   GstFormat format = GST_FORMAT_TIME;
122   qint64 dur = 0;
123   if (!gst_element_query_duration(m_bin, &format, &dur)) {
124     qmlInfo(this) << "Failed to query pipeline duration";
125     return 0;
126   }
127
128   if (format != GST_FORMAT_TIME) {
129     qmlInfo(this) << "Pipeline format is not time";
130     return 0;
131   }
132
133   dur /= 1000000;
134
135   return dur;
136 }
137
138 qint64 VideoPlayer::position() {
139   if (!m_bin) {
140     return 0;
141   }
142
143   GstFormat format = GST_FORMAT_TIME;
144   qint64 pos = 0;
145   if (!gst_element_query_position(m_bin, &format, &pos)) {
146     qmlInfo(this) << "Failed to query pipeline position";
147     return m_pos;
148   }
149
150   if (format != GST_FORMAT_TIME) {
151     qmlInfo(this) << "Pipeline format is not time";
152     return m_pos;
153   }
154
155   pos /= 1000000;
156
157   m_pos = pos;
158
159   return pos;
160 }
161
162 void VideoPlayer::setPosition(qint64 position) {
163   seek(position);
164 }
165
166 bool VideoPlayer::pause() {
167   return setState(VideoPlayer::StatePaused);
168 }
169
170 bool VideoPlayer::play() {
171   return setState(VideoPlayer::StatePlaying);
172 }
173
174 bool VideoPlayer::seek(qint64 offset) {
175   if (!m_bin) {
176     qmlInfo(this) << "no playbin2";
177     return false;
178   }
179
180   qint64 pos = offset;
181
182   offset *= 1000000;
183
184   GstSeekFlags flags = (GstSeekFlags)(GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE);
185   gboolean ret = gst_element_seek_simple (m_bin, GST_FORMAT_TIME,
186                                           flags, offset);
187
188   if (ret) {
189     m_pos = pos;
190
191     return TRUE;
192   }
193
194   return FALSE;
195 }
196
197 bool VideoPlayer::stop() {
198   return setState(VideoPlayer::StateStopped);
199 }
200
201 void VideoPlayer::geometryChanged(const QRectF& newGeometry, const QRectF& oldGeometry) {
202   QDeclarativeItem::geometryChanged(newGeometry, oldGeometry);
203
204   if (m_vf) {
205     m_vf->resize(newGeometry.size());
206   }
207 }
208
209 VideoPlayer::State VideoPlayer::state() const {
210   return m_state;
211 }
212
213 bool VideoPlayer::setState(const VideoPlayer::State& state) {
214   if (state == m_state) {
215     return true;
216   }
217
218   if (!m_bin) {
219     qmlInfo(this) << "no playbin2";
220     return false;
221   }
222
223   if (state == VideoPlayer::StatePaused) {
224     m_timer->stop();
225
226     // Set uri if needed:
227     if (m_state == VideoPlayer::StateStopped) {
228       const char *uri = m_url.toString().toUtf8().constData();
229       g_object_set(m_bin, "uri", uri, NULL);
230     }
231
232     if (gst_element_set_state(m_bin, GST_STATE_PAUSED) == GST_STATE_CHANGE_FAILURE) {
233       qmlInfo(this) << "error setting pipeline to PAUSED";
234       return false;
235     }
236
237     GstState st;
238     if (gst_element_get_state(m_bin, &st, NULL, GST_CLOCK_TIME_NONE)
239         == GST_STATE_CHANGE_FAILURE) {
240       qmlInfo(this) << "setting pipeline to PAUSED failed";
241       return false;
242     }
243
244     if (st != GST_STATE_PAUSED) {
245       qmlInfo(this) << "pipeline failed to transition to to PAUSED state";
246       return false;
247     }
248
249     m_state = state;
250     emit stateChanged();
251
252     return true;
253   }
254   else if (state == VideoPlayer::StatePlaying) {
255     // Set uri if needed:
256     if (m_state == VideoPlayer::StateStopped) {
257       const char *uri = m_url.toString().toUtf8().constData();
258       g_object_set(m_bin, "uri", uri, NULL);
259     }
260
261     if (gst_element_set_state(m_bin, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) {
262       qmlInfo(this) << "error setting pipeline to PLAYING";
263       return false;
264     }
265
266     GstState st;
267     if (gst_element_get_state(m_bin, &st, NULL, GST_CLOCK_TIME_NONE)
268         == GST_STATE_CHANGE_FAILURE) {
269       qmlInfo(this) << "setting pipeline to PLAYING failed";
270       return false;
271     }
272
273     if (st != GST_STATE_PLAYING) {
274       qmlInfo(this) << "pipeline failed to transition to to PLAYING state";
275       return false;
276     }
277
278     m_state = state;
279     emit stateChanged();
280
281     emit durationChanged();
282     emit positionChanged();
283
284     m_timer->start();
285     return true;
286   }
287   else {
288     m_timer->stop();
289     m_pos = 0;
290
291     if (gst_element_set_state(m_bin, GST_STATE_NULL) == GST_STATE_CHANGE_FAILURE) {
292       qmlInfo(this) << "error setting pipeline to NULL";
293       return false;
294     }
295
296     GstState st;
297     if (gst_element_get_state(m_bin, &st, NULL, GST_CLOCK_TIME_NONE)
298         == GST_STATE_CHANGE_FAILURE) {
299       qmlInfo(this) << "setting pipeline to NULL failed";
300       return false;
301     }
302
303     if (st != GST_STATE_NULL) {
304       qmlInfo(this) << "pipeline failed to transition to to NULL state";
305       return false;
306     }
307
308     m_state = state;
309     emit stateChanged();
310
311     emit durationChanged();
312     emit positionChanged();
313
314     return true;
315   }
316 }
317
318 gboolean VideoPlayer::bus_call(GstBus *bus, GstMessage *msg, gpointer data) {
319   Q_UNUSED(bus);
320
321   VideoPlayer *that = (VideoPlayer *) data;
322
323   gchar *debug = NULL;
324   GError *err = NULL;
325
326   switch (GST_MESSAGE_TYPE(msg)) {
327   case GST_MESSAGE_EOS:
328     that->stop();
329     break;
330
331   case GST_MESSAGE_ERROR:
332     gst_message_parse_error (msg, &err, &debug);
333
334     emit that->error(err->message, err->code, debug);
335     that->stop();
336     if (err) {
337       g_error_free (err);
338     }
339
340     if (debug) {
341       g_free (debug);
342     }
343
344     break;
345
346   default:
347     break;
348   }
349
350   return TRUE;  
351 }