Commit 0174e778 authored by Kevin Whitaker's avatar Kevin Whitaker

Finish implementing basic play, engine, and service.

parent 8bf9bef9
Pipeline #7 failed with stages
......@@ -13,7 +13,7 @@ include(KDECMakeSettings)
include(KDECompilerSettings NO_POLICY_SCOPE)
include(FeatureSummary)
find_package(Qt5 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS Core)
find_package(Qt5 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS Core Multimedia)
find_package(KF5 ${KF_MIN_VERSION} REQUIRED COMPONENTS
CoreAddons
......
......@@ -11,6 +11,7 @@ kcoreaddons_desktop_to_json(plasma_engine_simplecast plasma-dataengine-simplecas
target_link_libraries( plasma_engine_simplecast
Qt5::Core
Qt5::Multimedia
KF5::Plasma
KF5::Service
KF5::I18n
......
/*
* Simple Media Player that runs off a playlist.
* Copyright (C) 2019 Kevin Whitaker <eyecreate@eyecreate.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*ByteArray(jsonTrack)
* This program 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
......@@ -17,17 +16,202 @@
*/
#include "minimediaplayer.h"
#include <QProcess>
#include <QJsonDocument>
#include <QJsonObject>
MiniMediaPlayer::MiniMediaPlayer(QObject *parent) : QObject(parent)
{
m_player = new QMediaPlayer(this,QMediaPlayer::StreamPlayback);
connect(m_player, SIGNAL(mediaStatusChanged(QMediaPlayer::MediaStatus)),this,SLOT(mediaStatusChanged(QMediaPlayer::MediaStatus)));
connect(m_player,SIGNAL(stateChanged(QMediaPlayer::State)),this,SLOT(mediaStateChanged(QMediaPlayer::State)));
connect(m_player,SIGNAL(durationChanged(qint64)),this,SLOT(durationChanged(qint64)));
connect(m_player,SIGNAL(positionChanged(qint64)),this,SLOT(positionChanged(qint64)));
}
bool MiniMediaPlayer::isPlaying()
{
return m_player->state() == QMediaPlayer::State::PlayingState;
}
bool MiniMediaPlayer::nextTrack()
{
if(playlist.size() > 1)
{
playlist.pop_front();
m_player->stop();
m_player->setMedia(getStreamUrlFromUrl(playlist.first().second));
emit playlistChanged(getTrackNames());
return true;
}
return false;
}
bool MiniMediaPlayer::playPauseTrack(bool playing)
{
if(m_player->state() == QMediaPlayer::State::PlayingState && !playing)
{
m_player->pause();
return true;
}
else if(m_player->state() == QMediaPlayer::State::PausedState && playing)
{
m_player->play();
return true;
}
return false;
}
QList<QString> MiniMediaPlayer::getTrackNames()
{
QList<QString> playlistTitles;
for(QPair<QString,QUrl> item: playlist)
{
playlistTitles.append(item.first);
}
return playlistTitles;
}
QMap<QString, qint64> MiniMediaPlayer::currentState()
{
QMap<QString,qint64> state;
state.insert("duration",m_player->duration());
state.insert("position",m_player->position());
state.insert("state",isPlaying());
return state;
}
void MiniMediaPlayer::durationChanged(qint64 length)
{
emit trackDurationChanged(length);
}
void MiniMediaPlayer::positionChanged(qint64 position)
{
emit trackPositionChanged(position);
}
QMediaPlaylist* MiniMediaPlayer::playlist() const
bool MiniMediaPlayer::addURLToPlaylist(QUrl mediaUrl)
{
return m_playlist;
if(isValidMediaUrl(mediaUrl))
{
bool shouldStartPlaying = false;
if(playlist.size() == 0) shouldStartPlaying = true;
QList<QPair<QString,QUrl>> tracks = getStreamTitlesAndUrlsFromUrl(mediaUrl);
playlist.append(tracks);
emit playlistChanged(getTrackNames());
if(shouldStartPlaying && playlist.size() > 0)
{
m_player->setMedia(getStreamUrlFromUrl(playlist.first().second));
}
if(tracks.size() > 0)
{
return true;
}
}
return false;
}
QMediaPlayer* MiniMediaPlayer::player() const
QUrl MiniMediaPlayer::getStreamUrlFromUrl(QUrl mediaUrl)
{
return m_player;
//Run youtube-dl on Url and return Url given back.
QProcess youtubedl;
youtubedl.start("youtube-dl -f bestaudio -g "+mediaUrl.toString());
if(youtubedl.waitForFinished(-1))
{
QByteArray out = youtubedl.readAllStandardOutput();
QString outStr = QString::fromStdString(out.toStdString());
for(QString track:outStr.split('\n'))
{
qDebug() << "Found URL stream:" << track;
return QUrl(track);
}
}
return QUrl();
}
bool MiniMediaPlayer::isValidMediaUrl(QUrl mediaUrl)
{
//Only support youtube and bandcamp for now.
if(mediaUrl.host().endsWith("youtube.com") || mediaUrl.host().endsWith("bandcamp.com"))
{
//TODO: check more than host?
return true;
}
return false;
}
void MiniMediaPlayer::mediaStateChanged(QMediaPlayer::State state)
{
if(state == QMediaPlayer::State::PlayingState)
{
emit playStateChanged(true);
}
else
{
emit playStateChanged(false);
}
}
void MiniMediaPlayer::mediaStatusChanged(QMediaPlayer::MediaStatus status)
{
//Make sure to load next track once track has ended.
if(status == QMediaPlayer::MediaStatus::EndOfMedia)
{
//Load next track
nextTrack();
}
else if(status == QMediaPlayer::MediaStatus::InvalidMedia)
{
qWarning() << "Unable to play track.";
}
else if(status == QMediaPlayer::MediaStatus::LoadedMedia)
{
//Start playing media
m_player->play();
}
}
QList<QPair<QString,QUrl>> MiniMediaPlayer::getStreamTitlesAndUrlsFromUrl(QUrl mediaUrl)
{
QList<QPair<QString,QUrl>> tracks;
//Run youtube-dl on Url and return json given back.
QProcess youtubedl;
youtubedl.start("youtube-dl -f bestaudio -j "+mediaUrl.toString());
if(youtubedl.waitForFinished(-1))
{
//If there is anything in error output, error must have occured.
if(youtubedl.readAllStandardError().size()== 0)
{
QByteArray out = youtubedl.readAllStandardOutput();
QString outStr = QString::fromStdString(out.toStdString());
for(QString jsonTrack:outStr.split('\n'))
{
//Parse urls and titles from json list
QJsonDocument info = QJsonDocument::fromJson(jsonTrack.toUtf8());
if(!info.isNull() && info.isObject())
{
QPair<QString,QUrl> track;
QJsonObject jsonTrackInfo = info.object();
track.first = jsonTrackInfo["title"].toString();
track.second = QUrl(jsonTrackInfo["webpage_url"].toString());
tracks.append(track);
}
}
}
else
{
qWarning() << "Url may be invalid.";
}
}
return tracks;
}
/*
* Simple Media Player that runs off a playlist.
* Copyright (C) 2019 Kevin Whitaker <eyecreate@eyecreate.org>
*
* This program is free software: you can redistribute it and/or modify
......@@ -21,36 +20,49 @@
#include <QObject>
#include <QtMultimedia/QMediaPlayer>
#include <QtMultimedia/QMediaPlaylist>
/**
* @todo write docs
* Mini Media Player is a simple class that wraps some wrappers around Qt's Multimedia classes for adding and playing media from a playlist. Also parses URLs to be added to playlist using youtube-dl and makes sure they are valid.
*/
class MiniMediaPlayer : public QObject
{
Q_OBJECT
Q_PROPERTY(QMediaPlaylist playlist READ playlist)
Q_PROPERTY(QMediaPlayer player READ player)
public:
/**
* @todo write docs
*/
MiniMediaPlayer(QObject *parent = 0);
bool addURLToPlaylist(QUrl mediaUrl);
/**
* @return the playlist
*/
QMediaPlaylist *playlist() const;
/**
* @return the player
*/
QMediaPlayer *player() const;
QList<QString> getTrackNames();
bool isPlaying();
QMap<QString,qint64> currentState();
bool nextTrack();
bool playPauseTrack(bool playing);
signals:
void playStateChanged(bool isPlaying);
void playlistChanged(QStringList trackTitles);
void trackDurationChanged(qint64 length);
void trackPositionChanged(qint64 position);
private slots:
void mediaStatusChanged(QMediaPlayer::MediaStatus status);
void mediaStateChanged(QMediaPlayer::State state);
void durationChanged(qint64 length);
void positionChanged(qint64 position);
private:
QMediaPlaylist *m_playlist;
QMediaPlayer *m_player;
QList<QPair<QString,QUrl>> playlist;
bool isValidMediaUrl(QUrl mediaUrl);
QUrl getStreamUrlFromUrl(QUrl mediaUrl);
QList<QPair<QString,QUrl>> getStreamTitlesAndUrlsFromUrl(QUrl mediaUrl);
};
#endif // MINIMEDIAPLAYER_H
......@@ -22,25 +22,55 @@
#include "simplecastservice.h"
SimpleCastEngine::SimpleCastEngine(QObject *parent, const QVariantList &args)
: Plasma::DataEngine(parent,args)
: Plasma::DataEngine(parent,args), player(new MiniMediaPlayer(this))
{
Q_UNUSED(args)
setMinimumPollingInterval(333);
connect(player,SIGNAL(playStateChanged(bool)),this,SLOT(playStateChanged(bool)));
connect(player,SIGNAL(trackDurationChanged(qint64)),this,SLOT(durationChanged(qint64)));
connect(player,SIGNAL(trackPositionChanged(qint64)),this,SLOT(positionChanged(qint64)));
connect(player,SIGNAL(playlistChanged(QStringList)),this,SLOT(playlistChanged(QStringList)));
}
void SimpleCastEngine::durationChanged(qint64 length)
{
setData("CurrentState","trackDuration",length);
}
void SimpleCastEngine::playStateChanged(bool isPlaying)
{
setData("CurrentState","playingState",isPlaying);
}
void SimpleCastEngine::positionChanged(qint64 position)
{
setData("CurrentState","trackPosition",position);
}
bool SimpleCastEngine::sourceRequestEvent(const QString& source)
{
setData(source, "you found me");
return true;
//return updateSourceEvent(source);//TODO
return updateSourceEvent(source);
}
void SimpleCastEngine::playlistChanged(QStringList playlist)
{
setData("Playlist",playlist);
}
bool SimpleCastEngine::updateSourceEvent(const QString& source)
{
//Source for current playlist(contains title and url)
//Source for current track time and length, and playing state
setData(source, "you found me again");
return true;//TODO
if(source == "Playlist")
{
setData(source, QStringList(player->getTrackNames()));
}
else if(source == "CurrentState")
{
QMap<QString,qint64> state = player->currentState();
setData(source,"trackDuration",state["duration"]);
setData(source,"trackPosition",state["position"]);
setData(source,"playingState",state["state"]);
}
return true;
}
QStringList SimpleCastEngine::sources() const
......@@ -52,7 +82,7 @@ Plasma::Service * SimpleCastEngine::serviceForSource(const QString& source)
{
if(source == "CurrentState")
{
return new SimpleCastService(source, this);
return new SimpleCastService(source, player, this);
}
return Plasma::DataEngine::serviceForSource(source);
}
......
......@@ -24,7 +24,11 @@
#include <Plasma/DataEngine>
#include <qt5/QtCore/QString>
#include "minimediaplayer.h"
/**
* Simple Cast Engine is a Plasma DataEngine that conveys that status on the media player casting to to other code like Plasmoids.
*/
class SimpleCastEngine : public Plasma::DataEngine
{
Q_OBJECT
......@@ -33,9 +37,15 @@ public:
QStringList sources() const override;
Plasma::Service *serviceForSource(const QString &source) override;
private slots:
void durationChanged(qint64 length);
void positionChanged(qint64 position);
void playStateChanged(bool isPlaying);
void playlistChanged(QStringList playlist);
protected:
bool sourceRequestEvent(const QString &source) override;
bool updateSourceEvent(const QString &source) override;
MiniMediaPlayer *player;
};
......
/*
* <one line to give the program's name and a brief idea of what it does.>
* Copyright (C) 2019 <copyright holder> <email>
* Copyright (C) 2019 Kevin Whitaker <eyecreate@eyecreate.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
......@@ -17,8 +16,42 @@
*/
#include "simplecastservice.h"
#include <Plasma/ServiceJob>
SimpleCastService::SimpleCastService(const QString &destination, QObject *parent) : Plasma::Service(parent)
class SimpleCastJob : public Plasma::ServiceJob
{
Q_OBJECT
public:
SimpleCastJob(const QString& destination, const QString& operation, MiniMediaPlayer *player, const QVariantMap& parameters, QObject* parent = 0) : Plasma::ServiceJob(destination,operation,parameters,parent),player(player) {}
virtual void start() override
{
const auto operation = operationName();
const auto paramaters = parameters();
if(operation == "changePlayState")
{
if(paramaters.contains("playing"))
{
setResult(player->playPauseTrack(paramaters["playing"].toBool()));
}
}
else if(operation == "skipToNextTrack")
{
setResult(player->nextTrack());
}
else if(operation == "addURLToPlaylist")
{
if(paramaters.contains("URL"))
{
setResult(player->addURLToPlaylist(paramaters["URL"].toUrl()));
}
}
}
private:
MiniMediaPlayer *player;
};
SimpleCastService::SimpleCastService(const QString &destination,MiniMediaPlayer *player, QObject *parent) : Plasma::Service(parent), player(player)
{
setName("simplecastcontrol");
}
......@@ -30,7 +63,7 @@ SimpleCastService::~SimpleCastService()
Plasma::ServiceJob* SimpleCastService::createJob(const QString& operation, QVariantMap& parameters)
{
return NULL; //TODO
return new SimpleCastJob(destination(),operation,player,parameters,this);
}
//K_EXPORT_PLASMA_SERVICE(org.eyecreate.simplecast, SimpleCastService)
......
/*
* <one line to give the program's name and a brief idea of what it does.>
* Copyright (C) 2019 <copyright holder> <email>
* Copyright (C) 2019 Kevin Whitaker <eyecreate@eyecreate.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
......@@ -20,25 +19,20 @@
#define SIMPLECASTSERVICE_H
#include <Plasma/Service>
#include "minimediaplayer.h"
/**
* @todo write docs
* Simple Cast Service is a Plasma Service that controls the media player casting to.
*/
class SimpleCastService : public Plasma::Service
{
Q_OBJECT
public:
SimpleCastService(const QString &destination, QObject *parent = 0);
SimpleCastService(const QString &destination, MiniMediaPlayer *player, QObject *parent = 0);
~SimpleCastService();
protected:
/**
* @todo write docs
*
* @param operation TODO
* @param parameters TODO
* @return TODO
*/
Plasma::ServiceJob* createJob(const QString& operation, QVariantMap& parameters) override;
MiniMediaPlayer *player;
};
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment