698d0ed0a36fa9e304d9e2d852fcf6653dd154e7
[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     g_object_set (m_bin, "audio-sink", elem, NULL);
118   }
119
120   GstBus *bus = gst_element_get_bus(m_bin);
121   gst_bus_add_watch(bus, bus_call, this);
122   gst_object_unref(bus);
123 }
124
125 QUrl VideoPlayer::source() const {
126   return m_url;
127 }
128
129 void VideoPlayer::setSource(const QUrl& source) {
130   if (m_url != source) {
131     m_url = source;
132     emit sourceChanged();
133   }
134 }
135
136 CameraConfig *VideoPlayer::cameraConfig() const {
137   return m_config;  
138 }
139
140 void VideoPlayer::setCameraConfig(CameraConfig *config) {
141   if (m_config && m_config != config) {
142     qmlInfo(this) << "Cannot reset CameraConfig";
143     return;
144   }
145
146   if (!config) {
147     qmlInfo(this) << "CameraConfig cannot be empty";
148     return;
149   }
150
151   if (m_config != config) {
152     m_config = config;
153     emit cameraConfigChanged();
154   }
155 }
156
157 qint64 VideoPlayer::duration() const {
158   if (!m_bin) {
159     return 0;
160   }
161
162   GstFormat format = GST_FORMAT_TIME;
163   qint64 dur = 0;
164   if (!gst_element_query_duration(m_bin, &format, &dur)) {
165     qmlInfo(this) << "Failed to query pipeline duration";
166     return 0;
167   }
168
169   if (format != GST_FORMAT_TIME) {
170     qmlInfo(this) << "Pipeline format is not time";
171     return 0;
172   }
173
174   dur /= 1000000;
175
176   return dur;
177 }
178
179 qint64 VideoPlayer::position() {
180   if (!m_bin) {
181     return 0;
182   }
183
184   GstFormat format = GST_FORMAT_TIME;
185   qint64 pos = 0;
186   if (!gst_element_query_position(m_bin, &format, &pos)) {
187     qmlInfo(this) << "Failed to query pipeline position";
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 }