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