Don't forget to call base class implementation
[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 #if defined(QT4)
23 #include <QDeclarativeInfo>
24 #elif defined(QT5)
25 #include <QQmlInfo>
26 #endif
27 #include "cameraconfig.h"
28 #include <QTimer>
29 #include "qtcamviewfinderrenderer.h"
30 #include <QPainter>
31 #include <QMatrix4x4>
32
33 #if defined(QT4)
34 VideoPlayer::VideoPlayer(QDeclarativeItem *parent) :
35   QDeclarativeItem(parent),
36 #elif defined(QT5)
37 VideoPlayer::VideoPlayer(QQuickItem *parent) :
38   QQuickPaintedItem(parent),
39 #endif
40   m_config(0),
41   m_renderer(0),
42   m_bin(0),
43   m_state(VideoPlayer::StateStopped),
44   m_timer(new QTimer(this)),
45   m_pos(0) {
46
47   m_timer->setSingleShot(false);
48   m_timer->setInterval(500);
49   QObject::connect(m_timer, SIGNAL(timeout()), this, SIGNAL(positionChanged()));
50
51 #if defined(QT4)
52   setFlag(QGraphicsItem::ItemHasNoContents, false);
53 #endif
54
55 #if defined(QT5)
56   setRenderTarget(QQuickPaintedItem::FramebufferObject);
57   setSmooth(false);
58   setAntialiasing(false);
59 #endif
60 }
61
62 VideoPlayer::~VideoPlayer() {
63   stop();
64
65   if (m_bin) {
66     gst_object_unref(m_bin);
67     m_bin = 0;
68   }
69 }
70
71 void VideoPlayer::componentComplete() {
72 #if defined(QT4)
73   QDeclarativeItem::componentComplete();
74 #elif defined(QT5)
75   QQuickPaintedItem::componentComplete();
76 #endif
77
78   if (!m_config) {
79     qmlInfo(this) << "CameraConfig not set";
80     return;
81   }
82
83   m_renderer = QtCamViewfinderRenderer::create(m_config->config(), this);
84   if (!m_renderer) {
85     qmlInfo(this) << "Failed to create viewfinder renderer";
86     return;
87   }
88
89   m_renderer->resize(QSizeF(width(), height()));
90   QObject::connect(m_renderer, SIGNAL(updateRequested()), this, SLOT(updateRequested()));
91
92   if (m_bin) {
93     g_object_set(m_bin, "video-sink", m_renderer->sinkElement(), NULL);
94   }
95 }
96
97 void VideoPlayer::classBegin() {
98 #if defined(QT4)
99   QDeclarativeItem::classBegin();
100 #elif defined(QT5)
101   QQuickPaintedItem::classBegin();
102 #endif
103
104   m_bin = gst_element_factory_make ("playbin2", "VideoPlayerBin");
105   if (!m_bin) {
106     qmlInfo(this) << "Failed to create playbin2";
107     return;
108   }
109
110   g_object_set (m_bin, "flags", 99, NULL);
111
112   GstElement *elem = gst_element_factory_make("pulsesink", "VideoPlayerPulseSink");
113   if (!elem) {
114     qmlInfo(this) << "Failed to create pulsesink";
115   }
116   else {
117     // TODO: properties on sink
118     g_object_set (m_bin, "audio-sink", elem, NULL);
119   }
120
121   GstBus *bus = gst_element_get_bus(m_bin);
122   gst_bus_add_watch(bus, bus_call, this);
123   gst_object_unref(bus);
124 }
125
126 QUrl VideoPlayer::source() const {
127   return m_url;
128 }
129
130 void VideoPlayer::setSource(const QUrl& source) {
131   if (m_url != source) {
132     m_url = source;
133     emit sourceChanged();
134   }
135 }
136
137 CameraConfig *VideoPlayer::cameraConfig() const {
138   return m_config;  
139 }
140
141 void VideoPlayer::setCameraConfig(CameraConfig *config) {
142   if (m_config && m_config != config) {
143     qmlInfo(this) << "Cannot reset CameraConfig";
144     return;
145   }
146
147   if (!config) {
148     qmlInfo(this) << "CameraConfig cannot be empty";
149     return;
150   }
151
152   if (m_config != config) {
153     m_config = config;
154     emit cameraConfigChanged();
155   }
156 }
157
158 qint64 VideoPlayer::duration() const {
159   if (!m_bin) {
160     return 0;
161   }
162
163   GstFormat format = GST_FORMAT_TIME;
164   qint64 dur = 0;
165   if (!gst_element_query_duration(m_bin, &format, &dur)) {
166     qmlInfo(this) << "Failed to query pipeline duration";
167     return 0;
168   }
169
170   if (format != GST_FORMAT_TIME) {
171     qmlInfo(this) << "Pipeline format is not time";
172     return 0;
173   }
174
175   dur /= 1000000;
176
177   return dur;
178 }
179
180 qint64 VideoPlayer::position() {
181   if (!m_bin) {
182     return 0;
183   }
184
185   GstFormat format = GST_FORMAT_TIME;
186   qint64 pos = 0;
187   if (!gst_element_query_position(m_bin, &format, &pos)) {
188     qmlInfo(this) << "Failed to query pipeline position";
189     return m_pos;
190   }
191
192   if (format != GST_FORMAT_TIME) {
193     qmlInfo(this) << "Pipeline format is not time";
194     return m_pos;
195   }
196
197   pos /= 1000000;
198
199   m_pos = pos;
200
201   return pos;
202 }
203
204 void VideoPlayer::setPosition(qint64 position) {
205   seek(position);
206 }
207
208 bool VideoPlayer::pause() {
209   return setState(VideoPlayer::StatePaused);
210 }
211
212 bool VideoPlayer::play() {
213   return setState(VideoPlayer::StatePlaying);
214 }
215
216 bool VideoPlayer::seek(qint64 offset) {
217   if (!m_bin) {
218     qmlInfo(this) << "no playbin2";
219     return false;
220   }
221
222   qint64 pos = offset;
223
224   offset *= 1000000;
225
226   GstSeekFlags flags = (GstSeekFlags)(GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE);
227   gboolean ret = gst_element_seek_simple (m_bin, GST_FORMAT_TIME,
228                                           flags, offset);
229
230   if (ret) {
231     m_pos = pos;
232
233     return TRUE;
234   }
235
236   return FALSE;
237 }
238
239 bool VideoPlayer::stop() {
240   return setState(VideoPlayer::StateStopped);
241 }
242
243 void VideoPlayer::geometryChanged(const QRectF& newGeometry, const QRectF& oldGeometry) {
244 #if defined(QT4)
245   QDeclarativeItem::geometryChanged(newGeometry, oldGeometry);
246 #elif defined(QT5)
247   QQuickPaintedItem::geometryChanged(newGeometry, oldGeometry);
248 #endif
249
250   if (m_renderer) {
251     m_renderer->resize(newGeometry.size());
252   }
253 }
254
255 #if defined(QT4)
256 void VideoPlayer::paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
257                        QWidget *widget) {
258
259   Q_UNUSED(widget);
260   Q_UNUSED(option);
261
262   painter->fillRect(boundingRect(), Qt::black);
263
264 #elif defined(QT5)
265 void VideoPlayer::paint(QPainter *painter) {
266   painter->fillRect(contentsBoundingRect(), Qt::black);
267
268 #endif
269
270   if (!m_renderer) {
271     return;
272   }
273
274   bool needsNativePainting = m_renderer->needsNativePainting();
275
276   if (needsNativePainting) {
277     painter->beginNativePainting();
278   }
279
280   m_renderer->paint(QMatrix4x4(painter->combinedTransform()), painter->viewport());
281
282   if (needsNativePainting) {
283     painter->endNativePainting();
284   }
285 }
286
287 VideoPlayer::State VideoPlayer::state() const {
288   return m_state;
289 }
290
291 bool VideoPlayer::setState(const VideoPlayer::State& state) {
292   if (state == m_state) {
293     return true;
294   }
295
296   if (!m_bin) {
297     qmlInfo(this) << "no playbin2";
298     return false;
299   }
300
301   if (state == VideoPlayer::StatePaused) {
302     m_timer->stop();
303
304     // Set uri if needed:
305     if (m_state == VideoPlayer::StateStopped) {
306       const char *uri = m_url.toString().toUtf8().constData();
307       g_object_set(m_bin, "uri", uri, NULL);
308     }
309
310     if (gst_element_set_state(m_bin, GST_STATE_PAUSED) == GST_STATE_CHANGE_FAILURE) {
311       qmlInfo(this) << "error setting pipeline to PAUSED";
312       return false;
313     }
314
315     GstState st;
316     if (gst_element_get_state(m_bin, &st, NULL, GST_CLOCK_TIME_NONE)
317         == GST_STATE_CHANGE_FAILURE) {
318       qmlInfo(this) << "setting pipeline to PAUSED failed";
319       return false;
320     }
321
322     if (st != GST_STATE_PAUSED) {
323       qmlInfo(this) << "pipeline failed to transition to to PAUSED state";
324       return false;
325     }
326
327     m_state = state;
328     emit stateChanged();
329
330     return true;
331   }
332   else if (state == VideoPlayer::StatePlaying) {
333     // Set uri if needed:
334     if (m_state == VideoPlayer::StateStopped) {
335       const char *uri = m_url.toString().toUtf8().constData();
336       g_object_set(m_bin, "uri", uri, NULL);
337     }
338
339     if (gst_element_set_state(m_bin, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) {
340       qmlInfo(this) << "error setting pipeline to PLAYING";
341       return false;
342     }
343
344     GstState st;
345     if (gst_element_get_state(m_bin, &st, NULL, GST_CLOCK_TIME_NONE)
346         == GST_STATE_CHANGE_FAILURE) {
347       qmlInfo(this) << "setting pipeline to PLAYING failed";
348       return false;
349     }
350
351     if (st != GST_STATE_PLAYING) {
352       qmlInfo(this) << "pipeline failed to transition to to PLAYING state";
353       return false;
354     }
355
356     m_state = state;
357     emit stateChanged();
358
359     emit durationChanged();
360     emit positionChanged();
361
362     m_timer->start();
363     return true;
364   }
365   else {
366     m_timer->stop();
367     m_pos = 0;
368
369     if (gst_element_set_state(m_bin, GST_STATE_NULL) == GST_STATE_CHANGE_FAILURE) {
370       qmlInfo(this) << "error setting pipeline to NULL";
371       return false;
372     }
373
374     GstState st;
375     if (gst_element_get_state(m_bin, &st, NULL, GST_CLOCK_TIME_NONE)
376         == GST_STATE_CHANGE_FAILURE) {
377       qmlInfo(this) << "setting pipeline to NULL failed";
378       return false;
379     }
380
381     if (st != GST_STATE_NULL) {
382       qmlInfo(this) << "pipeline failed to transition to to NULL state";
383       return false;
384     }
385
386     m_state = state;
387     emit stateChanged();
388
389     emit durationChanged();
390     emit positionChanged();
391
392     return true;
393   }
394 }
395
396 gboolean VideoPlayer::bus_call(GstBus *bus, GstMessage *msg, gpointer data) {
397   Q_UNUSED(bus);
398
399   VideoPlayer *that = (VideoPlayer *) data;
400
401   gchar *debug = NULL;
402   GError *err = NULL;
403
404   switch (GST_MESSAGE_TYPE(msg)) {
405   case GST_MESSAGE_EOS:
406     that->stop();
407     break;
408
409   case GST_MESSAGE_ERROR:
410     gst_message_parse_error (msg, &err, &debug);
411
412     emit that->error(err->message, err->code, debug);
413     that->stop();
414     if (err) {
415       g_error_free (err);
416     }
417
418     if (debug) {
419       g_free (debug);
420     }
421
422     break;
423
424   default:
425     break;
426   }
427
428   return TRUE;  
429 }
430
431 void VideoPlayer::updateRequested() {
432   update();
433 }