diff --git a/harbour-nayttamo.pro b/harbour-nayttamo.pro index 840b556..511c41a 100644 --- a/harbour-nayttamo.pro +++ b/harbour-nayttamo.pro @@ -15,9 +15,11 @@ TARGET = harbour-nayttamo CONFIG += sailfishapp libcrypto SOURCES += src/harbour-nayttamo.cpp \ - src/urldecrypt.cpp + src/urldecrypt.cpp \ + src/serializer.cpp DISTFILES += \ + qml/components/ContinueWatchingDialog.qml \ rpm/harbour-nayttamo.changes.in \ rpm/harbour-nayttamo.spec \ rpm/harbour-nayttamo.yaml \ @@ -76,6 +78,7 @@ REQUIRED = $$find(DEFINES, "APP_ID") $$find(DEFINES, "APP_KEY") $$find(DEFINES, } HEADERS += \ - src/urldecrypt.h + src/urldecrypt.h \ + src/serializer.h unix: PKGCONFIG += libcrypto diff --git a/qml/components/ContinueWatchingDialog.qml b/qml/components/ContinueWatchingDialog.qml new file mode 100644 index 0000000..9c36e13 --- /dev/null +++ b/qml/components/ContinueWatchingDialog.qml @@ -0,0 +1,5 @@ +import QtQuick 2.0 + +Item { + +} diff --git a/qml/main.qml b/qml/main.qml index 95b18dc..2a44150 100644 --- a/qml/main.qml +++ b/qml/main.qml @@ -1,6 +1,7 @@ import QtQuick 2.2 import Sailfish.Silica 1.0 import QtMultimedia 5.5 + import "pages" ApplicationWindow @@ -11,6 +12,49 @@ ApplicationWindow property string coverTitle: "" property string coverSubTitle: "" + property var state: null + + onStateChanged: { + if (state === null) { + console.log('initialization, do not set the state') + return + } + serializer && serializer.setState(state) + } + + Component.onCompleted: { + state = serializer.getState() + } + + function setState(path, value) { + function setStateRecursive(obj, path, value) { + if (!Array.isArray(path)) { + path = path.split('.') + } + + if (path.length === 1) { + obj[path[0]] = value + return + } + + if (!obj[path[0]]) { + obj[path[0]] = {} + } + + return setStateRecursive(obj[path[0]], path.slice(1), value) + } + + var newState = JSON.parse(JSON.stringify(state)) + console.log('start', JSON.stringify(newState)) + setStateRecursive(newState, path, value) + console.log("done", JSON.stringify(newState)) + state = newState + } + + function insertStartedProgram(program) { + setState('startedPrograms.' + program.id, program.progress) + } + function updateCover(newCoverMode, newCoverTitle, newCoverSubtitle) { coverMode = newCoverMode ? newCoverMode : "" coverTitle = newCoverTitle ? newCoverTitle : "" @@ -25,4 +69,3 @@ ApplicationWindow cover: Qt.resolvedUrl("cover/CoverPage.qml") allowedOrientations: defaultAllowedOrientations } - diff --git a/qml/pages/PlayerPage.qml b/qml/pages/PlayerPage.qml index 7ccd2fa..960f65b 100644 --- a/qml/pages/PlayerPage.qml +++ b/qml/pages/PlayerPage.qml @@ -34,21 +34,42 @@ Page { if (visible) updateCover(qsTr("Now playing"), program.title, program.itemTitle) } + Component.onDestruction: { + appWindow.insertStartedProgram({ id: program.id, progress: mediaPlayer.position }) + } + function initialize() { YleApi.getMediaUrl(program.id, program.mediaId) - .then(function(response) { - if (response.subtitlesUrl && subtitlesText) { - subtitlesUrl = response.subtitlesUrl - subtitlesText.getSubtitles(subtitlesUrl) - } - mediaPlayer.source = response.url + .then(function(response) { + if (response.subtitlesUrl && subtitlesText) { + subtitlesUrl = response.subtitlesUrl + subtitlesText.getSubtitles(subtitlesUrl) + } + mediaPlayer.source = response.url + if (appWindow.state.startedPrograms[program.id]) { + var position = appWindow.state.startedPrograms[program.id] + var dialog = pageStack.push(Qt.resolvedUrl("ContinueWatchingDialog.qml"), + {"name": program.name, "position": position }) + dialog.accepted.connect(function() { + mediaPlayer.seek(position) + mediaPlayer.play() + YleApi.reportUsage(program.id, program.mediaId) + }) + + dialog.rejected.connect(function() { + mediaPlayer.play() + YleApi.reportUsage(program.id, program.mediaId) + }) + + } else { mediaPlayer.play() YleApi.reportUsage(program.id, program.mediaId) - }) - .catch(function(error) { - console.log("mediaUrlError", JSON.stringify(error)) - errorState = true; - }) + } + }) + .catch(function(error) { + console.log("mediaUrlError", JSON.stringify(error)) + errorState = true; + }) } // The effective value will be restricted by ApplicationWindow.allowedOrientations @@ -78,6 +99,7 @@ Page { source: mediaPlayer Component.onCompleted: initialize() + Component.onDestruction: { mediaPlayer.stop() mediaPlayer.source = "" @@ -117,12 +139,16 @@ Page { anchors.fill: parent onClicked: { mediaPlayer.playbackState === MediaPlayer.PlayingState - ? mediaPlayer.pause() - : mediaPlayer.play() + ? mediaPlayer.pause() + : mediaPlayer.play() if (subtitlesUrl && subtitlesText) { subtitlesText.getSubtitles(subtitlesUrl) } + + if (mediaPlayer.playbackState === MediaPlayer.PausedState) { + appWindow.insertStartedProgram({ id: program.id, progress: mediaPlayer.position }) + } } } } diff --git a/src/harbour-nayttamo.cpp b/src/harbour-nayttamo.cpp index ea4e905..d227088 100644 --- a/src/harbour-nayttamo.cpp +++ b/src/harbour-nayttamo.cpp @@ -40,6 +40,7 @@ #include #include "urldecrypt.h" +#include "serializer.h" int main(int argc, char *argv[]) { @@ -55,6 +56,15 @@ int main(int argc, char *argv[]) view->rootContext()->setContextProperty("urlDecrypt", &urlDecrypt); + auto worker = new QThread; + + QScopedPointer serializer(new Serializer); + view->rootContext()->setContextProperty("serializer", serializer.data()); + serializer->moveToThread(worker); + + QObject::connect(worker, SIGNAL(finished()), worker, SLOT(deleteLater())); + worker->start(); + view->engine()->addImportPath(SailfishApp::pathTo("qml/components").toString()); view->setSource(SailfishApp::pathTo("qml/main.qml")); diff --git a/src/serializer.cpp b/src/serializer.cpp new file mode 100644 index 0000000..7f1c58f --- /dev/null +++ b/src/serializer.cpp @@ -0,0 +1,84 @@ +#include "serializer.h" + +#include +#include +#include +#include +#include +#include +#include + +Serializer::Serializer(QObject *parent) : QObject(parent) +{ + this->ensureDir(); + this->readState(); +} + +Serializer::~Serializer() +{ + this->writeState(); +} + +QJsonObject Serializer::getInitialState() const +{ + return QJsonObject + { + {"startedPrograms", QJsonObject()} + }; +} + +bool Serializer::readState() +{ + QFile file(this->getPath()); + + if (!file.open(QIODevice::ReadWrite)) + { + return false; + } + + auto json = (QJsonDocument::fromJson(file.readAll())).object(); + + this->m_state = json.empty() ? this->getInitialState() : json; + + return true; +} + +bool Serializer::writeState() const +{ + QFile file(this->getPath()); + + if (!file.open(QIODevice::ReadWrite)) + { + return false; + } + + QJsonDocument saveDoc(m_state); + file.write(saveDoc.toJson()); + + return true; +} + +void Serializer::setState(QJsonObject state) +{ + m_state = state; +} + +QJsonObject Serializer::getState() +{ + return m_state; +} + +void Serializer::ensureDir() const +{ + QDir dir(QStandardPaths::writableLocation(QStandardPaths::DataLocation)); + + if (!dir.exists()) + { + dir.mkpath("."); + } +} + +QString Serializer::getPath() const +{ + return QStandardPaths::writableLocation(QStandardPaths::DataLocation) + QDir::separator() + "state.json"; +} diff --git a/src/serializer.h b/src/serializer.h new file mode 100644 index 0000000..5707462 --- /dev/null +++ b/src/serializer.h @@ -0,0 +1,29 @@ +#ifndef SERIALIZER_H +#define SERIALIZER_H + +#include +#include + +class Serializer : public QObject +{ + Q_OBJECT +public: + explicit Serializer(QObject *parent = 0); + ~Serializer(); + + Q_INVOKABLE void setState(QJsonObject state); + Q_INVOKABLE QJsonObject getState(); + +signals: +public slots: +private: + bool readState(); + bool writeState() const; + QString getPath() const; + void ensureDir() const; + QJsonObject getInitialState() const; + + QJsonObject m_state; +}; + +#endif // SERIALIZER_H