Initial implementation of our own VideoPlayer declarative item
authorMohammed Sameer <msameer@foolab.org>
Wed, 17 Jul 2013 23:42:25 +0000 (02:42 +0300)
committerMohammed Sameer <msameer@foolab.org>
Wed, 17 Jul 2013 23:42:25 +0000 (02:42 +0300)
15 files changed:
cameraplus.pro
declarative/camera.cpp
declarative/cameraconfig.cpp
declarative/declarative.pro
declarative/plugin.cpp
declarative/videoplayer.cpp [new file with mode: 0644]
declarative/videoplayer.h [new file with mode: 0644]
lib/qtcamviewfinderrenderermeego.cpp
qml/CameraView.qml
qml/PipelineManager.qml
qml/PostCaptureItem.qml
qml/PostCaptureView.qml
qml/VideoPlayerPage.qml
src/cameraresources.cpp
src/cameraresources.h

index beadc23..e00e15d 100644 (file)
@@ -1,4 +1,3 @@
 TEMPLATE = subdirs
 CONFIG += ordered
 SUBDIRS = lib declarative src
-
index 86ccaa4..f4e4a63 100644 (file)
@@ -75,6 +75,8 @@ Camera::Camera(QDeclarativeItem *parent) :
   m_videoTorch(0),
   m_config(new CameraConfig(this)) {
 
+  m_config->componentComplete();
+
   QObject::connect(m_vf, SIGNAL(renderAreaChanged()), this, SIGNAL(renderAreaChanged()));
   QObject::connect(m_vf, SIGNAL(videoResolutionChanged()), this, SIGNAL(videoResolutionChanged()));
   QObject::connect(m_vf, SIGNAL(renderingEnabledChanged()), this, SIGNAL(renderingEnabledChanged()));
index b247d62..6085ed5 100644 (file)
@@ -59,6 +59,10 @@ void CameraConfig::classBegin() {
 }
 
 void CameraConfig::componentComplete() {
+  if (m_config) {
+    return;
+  }
+
   if (m_path.isEmpty()) {
     m_config = new QtCamConfig(this);
   }
index 8b8c3a3..06a0a9e 100644 (file)
@@ -17,7 +17,7 @@ HEADERS += plugin.h previewprovider.h camera.h mode.h imagemode.h videomode.h \
            flickerreduction.h videomute.h metadata.h imagesettings.h \
            imageresolutionmodel.h videosettings.h videoresolutionmodel.h \
            notificationscontainer.h sounds.h focus.h autofocus.h \
-           roi.h cameraconfig.h
+           roi.h cameraconfig.h videoplayer.h
 
 SOURCES += plugin.cpp previewprovider.cpp camera.cpp mode.cpp imagemode.cpp videomode.cpp \
            zoom.cpp flash.cpp scene.cpp evcomp.cpp videotorch.cpp whitebalance.cpp \
@@ -25,7 +25,7 @@ SOURCES += plugin.cpp previewprovider.cpp camera.cpp mode.cpp imagemode.cpp vide
            flickerreduction.cpp videomute.cpp metadata.cpp imagesettings.cpp \
            imageresolutionmodel.cpp videosettings.cpp videoresolutionmodel.cpp \
            notificationscontainer.cpp sounds.cpp focus.cpp autofocus.cpp \
-           roi.cpp cameraconfig.cpp
+           roi.cpp cameraconfig.cpp videoplayer.cpp
 
 HEADERS += declarativeqtcameranotifications.h
 
index 56c23e2..1676e60 100644 (file)
@@ -47,6 +47,7 @@
 #include "declarativeqtcameranotifications.h"
 #include "sounds.h"
 #include "cameraconfig.h"
+#include "videoplayer.h"
 #include <QtDeclarative>
 
 #define MAJOR 1
@@ -68,6 +69,8 @@ void Plugin::initializeEngine(QDeclarativeEngine *engine, const char *uri) {
 }
 
 void Plugin::registerTypes(const char *uri) {
+  Q_ASSERT(QLatin1String(uri) == QLatin1String("QtCamera"));
+
   qmlRegisterType<Camera>(uri, MAJOR, MINOR, "Camera");
   qmlRegisterType<ImageMode>(uri, MAJOR, MINOR, "ImageMode");
   qmlRegisterType<VideoMode>(uri, MAJOR, MINOR, "VideoMode");
@@ -103,6 +106,8 @@ void Plugin::registerTypes(const char *uri) {
 
   qmlRegisterType<Mode>();
   qmlRegisterType<CameraConfig>(uri, MAJOR, MINOR, "CameraConfig");
+
+  qmlRegisterType<VideoPlayer>("QtCameraExtras", MAJOR, MINOR, "VideoPlayer");
 }
 
 Q_EXPORT_PLUGIN2(declarativeqtcamera, Plugin);
diff --git a/declarative/videoplayer.cpp b/declarative/videoplayer.cpp
new file mode 100644 (file)
index 0000000..29cfea4
--- /dev/null
@@ -0,0 +1,351 @@
+/*!
+ * This file is part of CameraPlus.
+ *
+ * Copyright (C) 2012-2013 Mohammed Sameer <msameer@foolab.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#include "videoplayer.h"
+#include <QDeclarativeInfo>
+#include "qtcamgraphicsviewfinder.h"
+#include "cameraconfig.h"
+#include <QTimer>
+
+VideoPlayer::VideoPlayer(QDeclarativeItem *parent) :
+  QDeclarativeItem(parent),
+  m_config(0),
+  m_vf(0),
+  m_bin(0),
+  m_state(VideoPlayer::StateStopped),
+  m_timer(new QTimer(this)),
+  m_pos(0) {
+
+  m_timer->setSingleShot(false);
+  m_timer->setInterval(500);
+  QObject::connect(m_timer, SIGNAL(timeout()), this, SIGNAL(positionChanged()));
+}
+
+VideoPlayer::~VideoPlayer() {
+  stop();
+
+  if (m_bin) {
+    gst_object_unref(m_bin);
+    m_bin = 0;
+  }
+}
+
+void VideoPlayer::componentComplete() {
+
+}
+
+void VideoPlayer::classBegin() {
+  m_bin = gst_element_factory_make ("playbin2", "VideoPlayerBin");
+  if (!m_bin) {
+    qmlInfo(this) << "Failed to create playbin2";
+    return;
+  }
+
+  g_object_set (m_bin, "flags", 99, NULL);
+
+  GstElement *elem = gst_element_factory_make("pulsesink", "VideoPlayerPulseSink");
+  if (!elem) {
+    qmlInfo(this) << "Failed to create pulsesink";
+  }
+  else {
+    // TODO: properties on sink
+    g_object_set (m_bin, "audio-sink", elem, NULL);
+  }
+
+  GstBus *bus = gst_element_get_bus(m_bin);
+  gst_bus_add_watch(bus, bus_call, this);
+  gst_object_unref(bus);
+}
+
+QUrl VideoPlayer::source() const {
+  return m_url;
+}
+
+void VideoPlayer::setSource(const QUrl& source) {
+  if (m_url != source) {
+    m_url = source;
+    emit sourceChanged();
+  }
+}
+
+CameraConfig *VideoPlayer::cameraConfig() const {
+  return m_config;  
+}
+
+void VideoPlayer::setCameraConfig(CameraConfig *config) {
+  if (m_config && m_config != config) {
+    qmlInfo(this) << "Cannot reset CameraConfig";
+    return;
+  }
+
+  if (!config) {
+    qmlInfo(this) << "CameraConfig cannot be empty";
+    return;
+  }
+
+  if (m_config != config) {
+    m_config = config;
+    // TODO: We need fence sync here.
+    m_vf = new QtCamGraphicsViewfinder(m_config->config(), this);
+    m_vf->resize(QSizeF(width(), height()));
+    emit cameraConfigChanged();
+  }
+
+  if (m_bin) {
+    g_object_set(m_bin, "video-sink", m_vf->sinkElement(), NULL);
+  }
+}
+
+qint64 VideoPlayer::duration() const {
+  if (!m_bin) {
+    return 0;
+  }
+
+  GstFormat format = GST_FORMAT_TIME;
+  qint64 dur = 0;
+  if (!gst_element_query_duration(m_bin, &format, &dur)) {
+    qmlInfo(this) << "Failed to query pipeline duration";
+    return 0;
+  }
+
+  if (format != GST_FORMAT_TIME) {
+    qmlInfo(this) << "Pipeline format is not time";
+    return 0;
+  }
+
+  dur /= 1000000;
+
+  return dur;
+}
+
+qint64 VideoPlayer::position() {
+  if (!m_bin) {
+    return 0;
+  }
+
+  GstFormat format = GST_FORMAT_TIME;
+  qint64 pos = 0;
+  if (!gst_element_query_position(m_bin, &format, &pos)) {
+    qmlInfo(this) << "Failed to query pipeline position";
+    return m_pos;
+  }
+
+  if (format != GST_FORMAT_TIME) {
+    qmlInfo(this) << "Pipeline format is not time";
+    return m_pos;
+  }
+
+  pos /= 1000000;
+
+  m_pos = pos;
+
+  return pos;
+}
+
+void VideoPlayer::setPosition(qint64 position) {
+  seek(position);
+}
+
+bool VideoPlayer::pause() {
+  return setState(VideoPlayer::StatePaused);
+}
+
+bool VideoPlayer::play() {
+  return setState(VideoPlayer::StatePlaying);
+}
+
+bool VideoPlayer::seek(qint64 offset) {
+  if (!m_bin) {
+    qmlInfo(this) << "no playbin2";
+    return false;
+  }
+
+  qint64 pos = offset;
+
+  offset *= 1000000;
+
+  GstSeekFlags flags = (GstSeekFlags)(GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE);
+  gboolean ret = gst_element_seek_simple (m_bin, GST_FORMAT_TIME,
+                                         flags, offset);
+
+  if (ret) {
+    m_pos = pos;
+
+    return TRUE;
+  }
+
+  return FALSE;
+}
+
+bool VideoPlayer::stop() {
+  return setState(VideoPlayer::StateStopped);
+}
+
+void VideoPlayer::geometryChanged(const QRectF& newGeometry, const QRectF& oldGeometry) {
+  QDeclarativeItem::geometryChanged(newGeometry, oldGeometry);
+
+  if (m_vf) {
+    m_vf->resize(newGeometry.size());
+  }
+}
+
+VideoPlayer::State VideoPlayer::state() const {
+  return m_state;
+}
+
+bool VideoPlayer::setState(const VideoPlayer::State& state) {
+  if (state == m_state) {
+    return true;
+  }
+
+  if (!m_bin) {
+    qmlInfo(this) << "no playbin2";
+    return false;
+  }
+
+  if (state == VideoPlayer::StatePaused) {
+    m_timer->stop();
+
+    // Set uri if needed:
+    if (m_state == VideoPlayer::StateStopped) {
+      const char *uri = m_url.toString().toUtf8().constData();
+      g_object_set(m_bin, "uri", uri, NULL);
+    }
+
+    if (gst_element_set_state(m_bin, GST_STATE_PAUSED) == GST_STATE_CHANGE_FAILURE) {
+      qmlInfo(this) << "error setting pipeline to PAUSED";
+      return false;
+    }
+
+    GstState st;
+    if (gst_element_get_state(m_bin, &st, NULL, GST_CLOCK_TIME_NONE)
+       == GST_STATE_CHANGE_FAILURE) {
+      qmlInfo(this) << "setting pipeline to PAUSED failed";
+      return false;
+    }
+
+    if (st != GST_STATE_PAUSED) {
+      qmlInfo(this) << "pipeline failed to transition to to PAUSED state";
+      return false;
+    }
+
+    m_state = state;
+    emit stateChanged();
+
+    return true;
+  }
+  else if (state == VideoPlayer::StatePlaying) {
+    // Set uri if needed:
+    if (m_state == VideoPlayer::StateStopped) {
+      const char *uri = m_url.toString().toUtf8().constData();
+      g_object_set(m_bin, "uri", uri, NULL);
+    }
+
+    if (gst_element_set_state(m_bin, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) {
+      qmlInfo(this) << "error setting pipeline to PLAYING";
+      return false;
+    }
+
+    GstState st;
+    if (gst_element_get_state(m_bin, &st, NULL, GST_CLOCK_TIME_NONE)
+       == GST_STATE_CHANGE_FAILURE) {
+      qmlInfo(this) << "setting pipeline to PLAYING failed";
+      return false;
+    }
+
+    if (st != GST_STATE_PLAYING) {
+      qmlInfo(this) << "pipeline failed to transition to to PLAYING state";
+      return false;
+    }
+
+    m_state = state;
+    emit stateChanged();
+
+    emit durationChanged();
+    emit positionChanged();
+
+    m_timer->start();
+    return true;
+  }
+  else {
+    m_timer->stop();
+    m_pos = 0;
+
+    if (gst_element_set_state(m_bin, GST_STATE_NULL) == GST_STATE_CHANGE_FAILURE) {
+      qmlInfo(this) << "error setting pipeline to NULL";
+      return false;
+    }
+
+    GstState st;
+    if (gst_element_get_state(m_bin, &st, NULL, GST_CLOCK_TIME_NONE)
+       == GST_STATE_CHANGE_FAILURE) {
+      qmlInfo(this) << "setting pipeline to NULL failed";
+      return false;
+    }
+
+    if (st != GST_STATE_NULL) {
+      qmlInfo(this) << "pipeline failed to transition to to NULL state";
+      return false;
+    }
+
+    m_state = state;
+    emit stateChanged();
+
+    emit durationChanged();
+    emit positionChanged();
+
+    return true;
+  }
+}
+
+gboolean VideoPlayer::bus_call(GstBus *bus, GstMessage *msg, gpointer data) {
+  Q_UNUSED(bus);
+
+  VideoPlayer *that = (VideoPlayer *) data;
+
+  gchar *debug = NULL;
+  GError *err = NULL;
+
+  switch (GST_MESSAGE_TYPE(msg)) {
+  case GST_MESSAGE_EOS:
+    that->stop();
+    break;
+
+  case GST_MESSAGE_ERROR:
+    gst_message_parse_error (msg, &err, &debug);
+
+    emit that->error(err->message, err->code, debug);
+    that->stop();
+    if (err) {
+      g_error_free (err);
+    }
+
+    if (debug) {
+      g_free (debug);
+    }
+
+    break;
+
+  default:
+    break;
+  }
+
+  return TRUE;  
+}
diff --git a/declarative/videoplayer.h b/declarative/videoplayer.h
new file mode 100644 (file)
index 0000000..388b621
--- /dev/null
@@ -0,0 +1,99 @@
+// -*- c++ -*-
+
+/*!
+ * This file is part of CameraPlus.
+ *
+ * Copyright (C) 2012-2013 Mohammed Sameer <msameer@foolab.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#ifndef VIDEO_PLAYER_H
+#define VIDEO_PLAYER_H
+
+#include <QDeclarativeItem>
+#include <gst/gst.h>
+
+class CameraConfig;
+class QtCamGraphicsViewfinder;
+
+class VideoPlayer : public QDeclarativeItem {
+  Q_OBJECT
+
+  Q_PROPERTY(QUrl source READ source WRITE setSource NOTIFY sourceChanged);
+  Q_PROPERTY(CameraConfig *cameraConfig READ cameraConfig WRITE setCameraConfig NOTIFY cameraConfigChanged);
+  Q_PROPERTY(qint64 duration READ duration NOTIFY durationChanged);
+  Q_PROPERTY(qint64 position READ position WRITE setPosition NOTIFY positionChanged);
+  Q_PROPERTY(State state READ state NOTIFY stateChanged);
+  Q_ENUMS(State);
+
+public:
+  VideoPlayer(QDeclarativeItem *parent = 0);
+  ~VideoPlayer();
+
+  virtual void componentComplete();
+  virtual void classBegin();
+
+  QUrl source() const;
+  void setSource(const QUrl& source);
+
+  CameraConfig *cameraConfig() const;
+  void setCameraConfig(CameraConfig *config);
+
+  qint64 duration() const;
+  qint64 position();
+  void setPosition(qint64 position);
+
+  Q_INVOKABLE bool pause();
+  Q_INVOKABLE bool play();
+  Q_INVOKABLE bool seek(qint64 offset);
+  Q_INVOKABLE bool stop();
+
+  typedef enum {
+    StateStopped,
+    StatePaused,
+    StatePlaying,
+  } State;
+
+  State state() const;
+
+signals:
+  void sourceChanged();
+  void cameraConfigChanged();
+
+  void durationChanged();
+  void positionChanged();
+  void error(const QString& message, int code, const QString& debug);
+  void stateChanged();
+
+protected:
+  void geometryChanged(const QRectF& newGeometry, const QRectF& oldGeometry);
+
+private:
+  static gboolean bus_call(GstBus *bus, GstMessage *msg, gpointer data);
+
+  bool setState(const State& state);
+
+  CameraConfig *m_config;
+  QtCamGraphicsViewfinder *m_vf;
+  QUrl m_url;
+
+  GstElement *m_bin;
+  State m_state;
+  QTimer *m_timer;
+  qint64 m_pos;
+};
+
+#endif /* VIDEO_PLAYER_H */
index ea2807d..f996c9e 100644 (file)
@@ -172,7 +172,14 @@ GstElement *QtCamViewfinderRendererMeeGo::sinkElement() {
   g_object_set(G_OBJECT(m_sink), "x-display", d, "use-framebuffer-memory", TRUE, NULL);
 
   m_dpy = eglGetDisplay((EGLNativeDisplayType)d);
+  if (m_dpy == EGL_NO_DISPLAY) {
+    qCritical() << "Failed to obtain EGL Display";
+  }
+
   EGLContext context = eglGetCurrentContext();
+  if (context == EGL_NO_CONTEXT) {
+    qCritical() << "Failed to obtain EGL context";
+  }
 
   g_object_set(G_OBJECT(m_sink), "egl-display", m_dpy, "egl-context", context, NULL);
 
index 13b2ec4..48b2855 100644 (file)
@@ -24,6 +24,7 @@ import QtQuick 1.1
 import QtCamera 1.0
 import CameraPlus 1.0
 
+// TODO: reset reticle and roi when we stop camera or change mode
 Camera {
     id: cam
 
index 458885a..d328711 100644 (file)
@@ -65,6 +65,9 @@ Item {
         } else if (!policy.acquire(currentItem.policyMode)) {
             console.log("Failed to acquire policy resources")
             return
+        } else if (currentItem.policyMode == CameraResources.Player) {
+            currentPolicyMode = CameraResources.Player
+            camera.stop(true)
         } else if (!camera.start()) {
             showError(qsTr("Failed to start camera. Please restart the application."))
         } else {
index af18fdb..6cd193e 100644 (file)
@@ -35,7 +35,9 @@ Item {
     function startPlayback() {
         loader.source = Qt.resolvedUrl("VideoPlayerPage.qml")
         loader.item.source = itemData.url
-        loader.item.play()
+        if (!loader.item.play()) {
+            loader.source = ""
+        }
     }
 
     Loader {
index e82d861..2d304b7 100644 (file)
@@ -27,9 +27,9 @@ import QtCamera 1.0
 
 Item {
     property bool pressed: view.currentItem ? view.currentItem.playing : false
-    property int policyMode: view.currentItem && view.currentItem.playing ? CameraResources.None :
-        settings.mode == Camera.VideoMode ? CameraResources.Video
-        CameraResources.Image
+    property int policyMode: view.currentItem && view.currentItem.playing ?
+        CameraResources.Player : settings.mode == Camera.VideoMode ? CameraResources.Video :
+        CameraResources.Image
     property bool available: view.currentItem ? view.currentItem.itemData.available : false
 
     Component.onCompleted: postCaptureModel.reload()
index fce5101..5fb702a 100644 (file)
@@ -22,8 +22,9 @@
 
 import QtQuick 1.1
 import com.nokia.meego 1.1
-import QtMultimediaKit 1.1
 import CameraPlus 1.0
+import QtCamera 1.0
+import QtCameraExtras 1.0
 
 // TODO: error reporting
 
@@ -34,7 +35,7 @@ Item {
     property alias source: video.source
 
     function play() {
-        video.play()
+        return video.play()
     }
 
     MouseArea {
@@ -53,9 +54,10 @@ Item {
         onTriggered: toolBar.show = false
     }
 
-    Video {
+    VideoPlayer {
         id: video
         anchors.fill: parent
+        cameraConfig: cam.cameraConfig
 
         function toggle() {
             if (!video.paused) {
@@ -65,7 +67,11 @@ Item {
             }
         }
 
-        onStopped: page.finished()
+        onStateChanged: {
+            if (state == VideoPlayer.StateStopped) {
+                page.finished()
+            }
+        }
     }
 
     Connections {
index 03db383..4e936e5 100644 (file)
@@ -216,6 +216,13 @@ void CameraResourcesWorker::acquire(bool *ok, const CameraResources::Mode& mode)
                    << ResourcePolicy::AudioPlaybackType);
     break;
 
+  case CameraResources::Player:
+    *ok = updateSet(QList<ResourcePolicy::ResourceType>()
+                   << ResourcePolicy::VideoPlaybackType
+                   << ResourcePolicy::AudioPlaybackType,
+                   QList<ResourcePolicy::ResourceType>());
+    break;
+
   default:
     qWarning() << "Unknown mode" << mode;
 
index 5183bb4..e31b9c1 100644 (file)
@@ -45,6 +45,7 @@ public:
     Image,
     Video,
     Recording,
+    Player,
   } Mode;
 
   CameraResources(QObject *parent = 0);