/* * Copyright (C) 2019 Kevin Whitaker * * 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "minimediaplayer.h" #include #include #include #include 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))); connect(m_player,&QMediaPlayer::volumeChanged,this,&MiniMediaPlayer::mediaVolumeChanged); } bool MiniMediaPlayer::isPlaying() { return m_player->state() == QMediaPlayer::State::PlayingState; } bool MiniMediaPlayer::nextTrack() { if(playlist.size() > 1) { playlist.pop_front(); m_player->pause(); m_player->setPosition(0); QtConcurrent::run([this]() { m_player->setMedia(getStreamUrlFromUrl(playlist.first().trackURL)); emit playlistChanged(getTrackNames()); }); return true; } else if(playlist.size() == 1) { playlist.pop_front(); m_player->stop(); emit playlistChanged(getTrackNames()); } return false; } qint64 MiniMediaPlayer::getCurrentPosition() { return m_player->position(); } qint64 MiniMediaPlayer::getTrackDuration() { return playlist.first().trackLength; } int MiniMediaPlayer::getVolume() { return m_player->volume(); } void MiniMediaPlayer::setVolume(int volume) { m_player->setVolume(volume); } 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 MiniMediaPlayer::getTrackNames() { QList playlistTitles; for(trackInfo item: playlist) { playlistTitles.append(item.trackName); } return playlistTitles; } QMap MiniMediaPlayer::currentState() { QMap state; if(playlist.size() < 1) { state.insert("duration",0); } else { state.insert("duration",playlist.first().trackLength); } state.insert("position",m_player->position()); state.insert("state",isPlaying()); return state; } void MiniMediaPlayer::durationChanged(qint64 length) { if(length == 0) { emit trackDurationChanged(playlist.first().trackLength); } else { emit trackDurationChanged(length); } } void MiniMediaPlayer::positionChanged(qint64 position) { emit trackPositionChanged(position); } void MiniMediaPlayer::addURLToPlaylist(QUrl mediaUrl) { QtConcurrent::run([this,mediaUrl](){ if(isValidMediaUrl(mediaUrl)) { bool shouldStartPlaying = false; if(playlist.size() == 0) shouldStartPlaying = true; QList tracks = getStreamTitlesAndUrlsFromUrl(mediaUrl); if(tracks.size() > 0) { playlist.append(tracks); emit playlistChanged(getTrackNames()); } if(shouldStartPlaying && playlist.size() > 0) { m_player->setMedia(getStreamUrlFromUrl(playlist.first().trackURL)); } } }); } QUrl MiniMediaPlayer::getStreamUrlFromUrl(QUrl mediaUrl) { //Run youtube-dl on Url and return Url given back. QProcess youtubedl; youtubedl.start("youtube-dl --no-warnings -f bestaudio[acodec=opus]/bestaudio[acodec=vorbis]/bestaudio[acodec=mp3] -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; } //For now, allow all urls since worst case youtube-dl will just fail to add anything to playlist. return true; } 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 if more in playlsit if(playlist.size() > 0) m_player->play(); } } QList MiniMediaPlayer::getStreamTitlesAndUrlsFromUrl(QUrl mediaUrl) { QList tracks; //Run youtube-dl on Url and return json given back. QProcess youtubedl; youtubedl.start("youtube-dl --no-warnings -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()) { trackInfo track; QJsonObject jsonTrackInfo = info.object(); track.trackName = jsonTrackInfo["title"].toString(); track.trackURL = QUrl(jsonTrackInfo["webpage_url"].toString()); track.trackLength = jsonTrackInfo["duration"].toInt() * 1000; //convert seconds to milliseconds tracks.append(track); } } } else { qDebug() << youtubedl.readAllStandardError(); qWarning() << "Url may be invalid."; } } return tracks; }