Add sound manager initial source code
[staging/soundmanager.git] / sample / mediaplayer / app / playlistwithmetadata.cpp
1 /*
2  * Copyright (C) 2016 The Qt Company Ltd.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 #include "playlistwithmetadata.h"
18
19 #include <QtCore/QDebug>
20 #include <QtCore/QBuffer>
21 #include <QtGui/QImage>
22 #include <QtGui/QImageWriter>
23 #include <QtMultimedia/QMediaPlayer>
24 #include <QtMultimedia/QMediaMetaData>
25
26 class PlaylistWithMetadata::Private
27 {
28 public:
29     Private(PlaylistWithMetadata *parent);
30
31     void disconnect();
32     void connect();
33
34 private:
35     void loadMetadata(int row);
36
37 private:
38     PlaylistWithMetadata *q;
39
40 public:
41     QAbstractListModel *source;
42     QList<QMetaObject::Connection> connections;
43     QList<QUrl> urls;
44     QHash<QUrl, QString> title;
45     QHash<QUrl, QString> artist;
46     QHash<QUrl, QUrl> coverArt;
47     QHash<QUrl, qint64> duration;
48     QHash<QUrl, QMediaPlayer *> players;
49 };
50
51 PlaylistWithMetadata::Private::Private(PlaylistWithMetadata *parent)
52     : q(parent)
53     , source(nullptr)
54 {
55 }
56
57 void PlaylistWithMetadata::Private::disconnect()
58 {
59     if (source) {
60         for (const auto &connection : connections)
61             q->disconnect(connection);
62         connections.clear();
63     }
64 }
65
66 void PlaylistWithMetadata::Private::connect()
67 {
68     if (source) {
69         connections.append(q->connect(source, &QAbstractListModel::rowsAboutToBeInserted, [&](const QModelIndex &parent, int first, int last) {
70             Q_UNUSED(parent)
71             q->beginInsertRows(QModelIndex(), first, last);
72         }));
73         connections.append(q->connect(source, &QAbstractListModel::rowsInserted, [&](const QModelIndex &parent, int first, int last) {
74             Q_UNUSED(parent)
75             for (int i = first; i <= last; i++) {
76                 loadMetadata(i);
77             }
78             q->endInsertRows();
79         }));
80
81         connections.append(q->connect(source, &QAbstractListModel::rowsAboutToBeRemoved, [&](const QModelIndex &parent, int first, int last) {
82             Q_UNUSED(parent)
83             q->beginRemoveRows(QModelIndex(), first, last);
84         }));
85         connections.append(q->connect(source, &QAbstractListModel::rowsRemoved, [&](const QModelIndex &parent, int first, int last) {
86             Q_UNUSED(parent)
87             for (int i = last; i >= first; --i) {
88                 QUrl url = urls.at(i);
89                 urls.removeAt(i);
90
91                 players.remove(url);
92                 title.remove(url);
93                 artist.remove(url);
94                 coverArt.remove(url);
95                 duration.remove(url);
96             }
97             q->endRemoveRows();
98         }));
99
100         int count = source->rowCount();
101         if (count > 0) {
102             q->beginInsertRows(QModelIndex(), 0, count);
103             for (int i = 0; i < count; i++) {
104                 loadMetadata(i);
105             }
106             q->endInsertRows();
107         }
108     }
109 }
110
111 void PlaylistWithMetadata::Private::loadMetadata(int row)
112 {
113     QUrl url = source->data(source->index(row), Qt::UserRole + 1).toUrl();
114     QMediaPlayer *player = new QMediaPlayer(q);
115     urls.append(url);
116     players.insert(url, player);
117     q->connect(player, &QMediaPlayer::mediaStatusChanged, [this, url](QMediaPlayer::MediaStatus mediaStatus) {
118         switch (mediaStatus) {
119         case QMediaPlayer::NoMedia:
120         case QMediaPlayer::LoadedMedia: {
121             QMediaPlayer *player = players.take(url);
122             title.insert(url, player->metaData(QMediaMetaData::Title).toString());
123             artist.insert(url, player->metaData(QMediaMetaData::ContributingArtist).toString());
124             QVariant coverArtImage = player->metaData(QMediaMetaData::CoverArtImage);
125             if (coverArtImage.type() == QVariant::Image) {
126                 QImage image = coverArtImage.value<QImage>();
127                 QByteArray data;
128                 QBuffer buffer(&data);
129                 buffer.open(QBuffer::WriteOnly);
130                 QImageWriter png(&buffer, "png");
131                 if (png.write(image)) {
132                     buffer.close();
133                     coverArt.insert(url, QUrl(QStringLiteral("data:image/png;base64,") + data.toBase64()));
134                 }
135             }
136             duration.insert(url, player->duration());
137             QModelIndex index = q->index(urls.indexOf(url));
138             q->dataChanged(index, index, QVector<int>() << TitleRole << ArtistRole << CoverArtRole << DurationRole);
139             player->deleteLater();
140             break; }
141         default:
142             break;
143         }
144
145     });
146     player->setMedia(url);
147 }
148
149 PlaylistWithMetadata::PlaylistWithMetadata(QObject *parent)
150     : QAbstractListModel(parent)
151     , d(new Private(this))
152 {
153 }
154
155 PlaylistWithMetadata::~PlaylistWithMetadata()
156 {
157     delete d;
158 }
159
160 int PlaylistWithMetadata::rowCount(const QModelIndex &parent) const
161 {
162     int ret = 0;
163     if (parent.isValid())
164         return ret;
165     if (d->source)
166         ret = d->source->rowCount(QModelIndex());
167     return ret;
168 }
169
170 QVariant PlaylistWithMetadata::data(const QModelIndex &index, int role) const
171 {
172     QVariant ret;
173     if (!index.isValid())
174         return ret;
175     int row = index.row();
176     if (row < 0 || rowCount() <= row)
177         return ret;
178     QUrl url = d->urls.at(row);
179     switch (role) {
180     case TitleRole:
181         ret = d->title.value(url);
182         if (ret.toString().isEmpty())
183             ret = QVariant(url.fileName());
184         break;
185     case ArtistRole:
186         ret = d->artist.value(url);
187         break;
188     case CoverArtRole:
189         ret = d->coverArt.value(url);
190         break;
191     case SourceRole:
192         ret = url;
193         break;
194     case DurationRole:
195         ret = d->duration.value(url);
196         break;
197     default:
198         qWarning() << role;
199     }
200
201     return ret;
202 }
203
204 QHash<int, QByteArray> PlaylistWithMetadata::roleNames() const
205 {
206     return {
207         {TitleRole, "title"},
208         {ArtistRole, "artist"},
209         {CoverArtRole, "coverArt"},
210         {SourceRole, "source"},
211         {DurationRole, "duration"}
212     };
213 }
214
215 QAbstractListModel *PlaylistWithMetadata::source() const
216 {
217     return d->source;
218 }
219
220 void PlaylistWithMetadata::setSource(QAbstractListModel *source)
221 {
222     if (d->source == source) return;
223     d->disconnect();
224     d->source = source;
225     d->connect();
226     emit sourceChanged(source);
227 }