Added the ability to control playback volume from post capture
[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     qmlInfo(this) << "Failed to query pipeline duration";
168     return 0;
169   }
170
171   if (format != GST_FORMAT_TIME) {
172     qmlInfo(this) << "Pipeline format is not time";
173     return 0;
174   }
175
176   dur /= 1000000;
177
178   return dur;
179 }
180
181 qint64 VideoPlayer::position() {
182   if (!m_bin) {
183     return 0;
184   }
185
186   GstFormat format = GST_FORMAT_TIME;
187   qint64 pos = 0;
188   if (!gst_element_query_position(m_bin, &format, &pos)) {
189     qmlInfo(this) << "Failed to query pipeline position";
190     return m_pos;
191   }
192
193   if (format != GST_FORMAT_TIME) {
194     qmlInfo(this) << "Pipeline format is not time";
195     return m_pos;
196   }
197
198   pos /= 1000000;
199
200   m_pos = pos;
201
202   return pos;
203 }
204
205 void VideoPlayer::setPosition(qint64 position) {
206   seek(position);
207 }
208
209 bool VideoPlayer::pause() {
210   return setState(VideoPlayer::StatePaused);
211 }
212
213 bool VideoPlayer::play() {
214   return setState(VideoPlayer::StatePlaying);
215 }
216
217 bool VideoPlayer::seek(qint64 offset) {
218   if (!m_bin) {
219     qmlInfo(this) << "no playbin2";
220     return false;
221   }
222
223   qint64 pos = offset;
224
225   offset *= 1000000;
226
227   GstSeekFlags flags = (GstSeekFlags)(GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE);
228   gboolean ret = gst_element_seek_simple (m_bin, GST_FORMAT_TIME,
229                                           flags, offset);
230
231   if (ret) {
232     m_pos = pos;
233
234     return TRUE;
235   }
236
237   return FALSE;
238 }
239
240 bool VideoPlayer::stop() {
241   return setState(VideoPlayer::StateStopped);
242 }
243
244 void VideoPlayer::geometryChanged(const QRectF& newGeometry, const QRectF& oldGeometry) {
245 #if defined(QT4)
246   QDeclarativeItem::geometryChanged(newGeometry, oldGeometry);
247 #elif defined(QT5)
248   QQuickPaintedItem::geometryChanged(newGeometry, oldGeometry);
249 #endif
250
251   if (m_renderer) {
252     m_renderer->resize(newGeometry.size());
253   }
254 }
255
256 #if defined(QT4)
257 void VideoPlayer::paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
258                        QWidget *widget) {
259
260   Q_UNUSED(widget);
261   Q_UNUSED(option);
262
263   painter->fillRect(boundingRect(), Qt::black);
264
265 #elif defined(QT5)
266 void VideoPlayer::paint(QPainter *painter) {
267   painter->fillRect(contentsBoundingRect(), Qt::black);
268
269 #endif
270
271   if (!m_renderer) {
272     return;
273   }
274
275   bool needsNativePainting = m_renderer->needsNativePainting();
276
277   if (needsNativePainting) {
278     painter->beginNativePainting();
279   }
280
281   m_renderer->paint(QMatrix4x4(painter->combinedTransform()), painter->viewport());
282
283   if (needsNativePainting) {
284     painter->endNativePainting();
285   }
286 }
287
288 VideoPlayer::State VideoPlayer::state() const {
289   return m_state;
290 }
291
292 bool VideoPlayer::setState(const VideoPlayer::State& state) {
293   if (state == m_state) {
294     return true;
295   }
296
297   if (!m_bin) {
298     qmlInfo(this) << "no playbin2";
299     return false;
300   }
301
302   if (state == VideoPlayer::StatePaused) {
303     m_timer->stop();
304
305     // Set uri if needed:
306     if (m_state == VideoPlayer::StateStopped) {
307       const char *uri = m_url.toString().toUtf8().constData();
308       g_object_set(m_bin, "uri", uri, NULL);
309     }
310
311     if (gst_element_set_state(m_bin, GST_STATE_PAUSED) == GST_STATE_CHANGE_FAILURE) {
312       qmlInfo(this) << "error setting pipeline to PAUSED";
313       return false;
314     }
315
316     GstState st;
317     if (gst_element_get_state(m_bin, &st, NULL, GST_CLOCK_TIME_NONE)
318         == GST_STATE_CHANGE_FAILURE) {
319       qmlInfo(this) << "setting pipeline to PAUSED failed";
320       return false;
321     }
322
323     if (st != GST_STATE_PAUSED) {
324       qmlInfo(this) << "pipeline failed to transition to to PAUSED state";
325       return false;
326     }
327
328     m_state = state;
329     emit stateChanged();
330
331     return true;
332   }
333   else if (state == VideoPlayer::StatePlaying) {
334     // Set uri if needed:
335     if (m_state == VideoPlayer::StateStopped) {
336       const char *uri = m_url.toString().toUtf8().constData();
337       g_object_set(m_bin, "uri", uri, NULL);
338     }
339
340     if (gst_element_set_state(m_bin, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) {
341       qmlInfo(this) << "error setting pipeline to PLAYING";
342       return false;
343     }
344
345     GstState st;
346     if (gst_element_get_state(m_bin, &st, NULL, GST_CLOCK_TIME_NONE)
347         == GST_STATE_CHANGE_FAILURE) {
348       qmlInfo(this) << "setting pipeline to PLAYING failed";
349       return false;
350     }
351
352     if (st != GST_STATE_PLAYING) {
353       qmlInfo(this) << "pipeline failed to transition to to PLAYING state";
354       return false;
355     }
356
357     m_state = state;
358     emit stateChanged();
359
360     emit durationChanged();
361     emit positionChanged();
362
363     m_timer->start();
364     return true;
365   }
366   else {
367     m_timer->stop();
368     m_pos = 0;
369
370     if (gst_element_set_state(m_bin, GST_STATE_NULL) == GST_STATE_CHANGE_FAILURE) {
371       qmlInfo(this) << "error setting pipeline to NULL";
372       return false;
373     }
374
375     GstState st;
376     if (gst_element_get_state(m_bin, &st, NULL, GST_CLOCK_TIME_NONE)
377         == GST_STATE_CHANGE_FAILURE) {
378       qmlInfo(this) << "setting pipeline to NULL failed";
379       return false;
380     }
381
382     if (st != GST_STATE_NULL) {
383       qmlInfo(this) << "pipeline failed to transition to to NULL state";
384       return false;
385     }
386
387     m_state = state;
388     emit stateChanged();
389
390     emit durationChanged();
391     emit positionChanged();
392
393     return true;
394   }
395 }
396
397 gboolean VideoPlayer::bus_call(GstBus *bus, GstMessage *msg, gpointer data) {
398   Q_UNUSED(bus);
399
400   VideoPlayer *that = (VideoPlayer *) data;
401
402   gchar *debug = NULL;
403   GError *err = NULL;
404
405   switch (GST_MESSAGE_TYPE(msg)) {
406   case GST_MESSAGE_EOS:
407     that->stop();
408     break;
409
410   case GST_MESSAGE_ERROR:
411     gst_message_parse_error (msg, &err, &debug);
412
413     emit that->error(err->message, err->code, debug);
414     that->stop();
415     if (err) {
416       g_error_free (err);
417     }
418
419     if (debug) {
420       g_free (debug);
421     }
422
423     break;
424
425   default:
426     break;
427   }
428
429   return TRUE;  
430 }
431
432 void VideoPlayer::updateRequested() {
433   update();
434 }
435
436 quint32 VideoPlayer::volume() {
437   double vol = 1.0;
438   g_object_get (m_bin, "volume", &vol, NULL);
439
440   qint32 res = (int)round(vol * 100.0);
441
442   return res;
443 }
444
445 void VideoPlayer::setVolume(quint32 volume) {
446   if (VideoPlayer::volume() != volume) {
447     double vol = volume / 100.0;
448     g_object_set (m_bin, "volume", vol, NULL);
449     emit volumeChanged();
450   }
451 }
452
453 void VideoPlayer::on_volume_changed(GObject *object, GParamSpec *pspec, gpointer user_data) {
454   Q_UNUSED(object);
455   Q_UNUSED(pspec);
456
457   VideoPlayer *player = (VideoPlayer *) user_data;
458
459   QMetaObject::invokeMethod(player, "volumeChanged", Qt::QueuedConnection);
460 }