Create a QDeclarativeItem Viewfinder subclass
[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     m_vf = new QtCamGraphicsViewfinder(m_config->config(), this);
106     m_vf->resize(QSizeF(width(), height()));
107     emit cameraConfigChanged();
108   }
109
110   if (m_bin) {
111     g_object_set(m_bin, "video-sink", m_vf->sinkElement(), NULL);
112   }
113 }
114
115 qint64 VideoPlayer::duration() const {
116   if (!m_bin) {
117     return 0;
118   }
119
120   GstFormat format = GST_FORMAT_TIME;
121   qint64 dur = 0;
122   if (!gst_element_query_duration(m_bin, &format, &dur)) {
123     qmlInfo(this) << "Failed to query pipeline duration";
124     return 0;
125   }
126
127   if (format != GST_FORMAT_TIME) {
128     qmlInfo(this) << "Pipeline format is not time";
129     return 0;
130   }
131
132   dur /= 1000000;
133
134   return dur;
135 }
136
137 qint64 VideoPlayer::position() {
138   if (!m_bin) {
139     return 0;
140   }
141
142   GstFormat format = GST_FORMAT_TIME;
143   qint64 pos = 0;
144   if (!gst_element_query_position(m_bin, &format, &pos)) {
145     qmlInfo(this) << "Failed to query pipeline position";
146     return m_pos;
147   }
148
149   if (format != GST_FORMAT_TIME) {
150     qmlInfo(this) << "Pipeline format is not time";
151     return m_pos;
152   }
153
154   pos /= 1000000;
155
156   m_pos = pos;
157
158   return pos;
159 }
160
161 void VideoPlayer::setPosition(qint64 position) {
162   seek(position);
163 }
164
165 bool VideoPlayer::pause() {
166   return setState(VideoPlayer::StatePaused);
167 }
168
169 bool VideoPlayer::play() {
170   return setState(VideoPlayer::StatePlaying);
171 }
172
173 bool VideoPlayer::seek(qint64 offset) {
174   if (!m_bin) {
175     qmlInfo(this) << "no playbin2";
176     return false;
177   }
178
179   qint64 pos = offset;
180
181   offset *= 1000000;
182
183   GstSeekFlags flags = (GstSeekFlags)(GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE);
184   gboolean ret = gst_element_seek_simple (m_bin, GST_FORMAT_TIME,
185                                           flags, offset);
186
187   if (ret) {
188     m_pos = pos;
189
190     return TRUE;
191   }
192
193   return FALSE;
194 }
195
196 bool VideoPlayer::stop() {
197   return setState(VideoPlayer::StateStopped);
198 }
199
200 void VideoPlayer::geometryChanged(const QRectF& newGeometry, const QRectF& oldGeometry) {
201   QDeclarativeItem::geometryChanged(newGeometry, oldGeometry);
202
203   if (m_vf) {
204     m_vf->resize(newGeometry.size());
205   }
206 }
207
208 VideoPlayer::State VideoPlayer::state() const {
209   return m_state;
210 }
211
212 bool VideoPlayer::setState(const VideoPlayer::State& state) {
213   if (state == m_state) {
214     return true;
215   }
216
217   if (!m_bin) {
218     qmlInfo(this) << "no playbin2";
219     return false;
220   }
221
222   if (state == VideoPlayer::StatePaused) {
223     m_timer->stop();
224
225     // Set uri if needed:
226     if (m_state == VideoPlayer::StateStopped) {
227       const char *uri = m_url.toString().toUtf8().constData();
228       g_object_set(m_bin, "uri", uri, NULL);
229     }
230
231     if (gst_element_set_state(m_bin, GST_STATE_PAUSED) == GST_STATE_CHANGE_FAILURE) {
232       qmlInfo(this) << "error setting pipeline to PAUSED";
233       return false;
234     }
235
236     GstState st;
237     if (gst_element_get_state(m_bin, &st, NULL, GST_CLOCK_TIME_NONE)
238         == GST_STATE_CHANGE_FAILURE) {
239       qmlInfo(this) << "setting pipeline to PAUSED failed";
240       return false;
241     }
242
243     if (st != GST_STATE_PAUSED) {
244       qmlInfo(this) << "pipeline failed to transition to to PAUSED state";
245       return false;
246     }
247
248     m_state = state;
249     emit stateChanged();
250
251     return true;
252   }
253   else if (state == VideoPlayer::StatePlaying) {
254     // Set uri if needed:
255     if (m_state == VideoPlayer::StateStopped) {
256       const char *uri = m_url.toString().toUtf8().constData();
257       g_object_set(m_bin, "uri", uri, NULL);
258     }
259
260     if (gst_element_set_state(m_bin, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) {
261       qmlInfo(this) << "error setting pipeline to PLAYING";
262       return false;
263     }
264
265     GstState st;
266     if (gst_element_get_state(m_bin, &st, NULL, GST_CLOCK_TIME_NONE)
267         == GST_STATE_CHANGE_FAILURE) {
268       qmlInfo(this) << "setting pipeline to PLAYING failed";
269       return false;
270     }
271
272     if (st != GST_STATE_PLAYING) {
273       qmlInfo(this) << "pipeline failed to transition to to PLAYING state";
274       return false;
275     }
276
277     m_state = state;
278     emit stateChanged();
279
280     emit durationChanged();
281     emit positionChanged();
282
283     m_timer->start();
284     return true;
285   }
286   else {
287     m_timer->stop();
288     m_pos = 0;
289
290     if (gst_element_set_state(m_bin, GST_STATE_NULL) == GST_STATE_CHANGE_FAILURE) {
291       qmlInfo(this) << "error setting pipeline to NULL";
292       return false;
293     }
294
295     GstState st;
296     if (gst_element_get_state(m_bin, &st, NULL, GST_CLOCK_TIME_NONE)
297         == GST_STATE_CHANGE_FAILURE) {
298       qmlInfo(this) << "setting pipeline to NULL failed";
299       return false;
300     }
301
302     if (st != GST_STATE_NULL) {
303       qmlInfo(this) << "pipeline failed to transition to to NULL state";
304       return false;
305     }
306
307     m_state = state;
308     emit stateChanged();
309
310     emit durationChanged();
311     emit positionChanged();
312
313     return true;
314   }
315 }
316
317 gboolean VideoPlayer::bus_call(GstBus *bus, GstMessage *msg, gpointer data) {
318   Q_UNUSED(bus);
319
320   VideoPlayer *that = (VideoPlayer *) data;
321
322   gchar *debug = NULL;
323   GError *err = NULL;
324
325   switch (GST_MESSAGE_TYPE(msg)) {
326   case GST_MESSAGE_EOS:
327     that->stop();
328     break;
329
330   case GST_MESSAGE_ERROR:
331     gst_message_parse_error (msg, &err, &debug);
332
333     emit that->error(err->message, err->code, debug);
334     that->stop();
335     if (err) {
336       g_error_free (err);
337     }
338
339     if (debug) {
340       g_free (debug);
341     }
342
343     break;
344
345   default:
346     break;
347   }
348
349   return TRUE;  
350 }