1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2016 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:LGPL$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU Lesser General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
21 | ** packaging of this file. Please review the following information to |
22 | ** ensure the GNU Lesser General Public License version 3 requirements |
23 | ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
24 | ** |
25 | ** GNU General Public License Usage |
26 | ** Alternatively, this file may be used under the terms of the GNU |
27 | ** General Public License version 2.0 or (at your option) the GNU General |
28 | ** Public license version 3 or any later version approved by the KDE Free |
29 | ** Qt Foundation. The licenses are as published by the Free Software |
30 | ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
31 | ** included in the packaging of this file. Please review the following |
32 | ** information to ensure the GNU General Public License requirements will |
33 | ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
34 | ** https://www.gnu.org/licenses/gpl-3.0.html. |
35 | ** |
36 | ** $QT_END_LICENSE$ |
37 | ** |
38 | ****************************************************************************/ |
39 | |
40 | #include "qplaylistfileparser_p.h" |
41 | #include <qfileinfo.h> |
42 | #include <QtCore/QDebug> |
43 | #include <QtCore/qiodevice.h> |
44 | #include <QtNetwork/QNetworkReply> |
45 | #include <QtNetwork/QNetworkRequest> |
46 | #include "qmediaplayer.h" |
47 | #include "qmediaobject_p.h" |
48 | #include "qmediametadata.h" |
49 | #include "qmediacontent.h" |
50 | #include "qmediaresource.h" |
51 | |
52 | QT_BEGIN_NAMESPACE |
53 | |
54 | namespace { |
55 | |
56 | class ParserBase |
57 | { |
58 | public: |
59 | explicit ParserBase(QPlaylistFileParser *parent) |
60 | : m_parent(parent) |
61 | , m_aborted(false) |
62 | { |
63 | Q_ASSERT(m_parent); |
64 | } |
65 | |
66 | bool parseLine(int lineIndex, const QString& line, const QUrl& root) |
67 | { |
68 | if (m_aborted) |
69 | return false; |
70 | |
71 | const bool ok = parseLineImpl(lineIndex, line, root); |
72 | return ok && !m_aborted; |
73 | } |
74 | |
75 | virtual void abort() { m_aborted = true; } |
76 | virtual ~ParserBase() { } |
77 | |
78 | protected: |
79 | virtual bool parseLineImpl(int lineIndex, const QString& line, const QUrl& root) = 0; |
80 | |
81 | static QUrl expandToFullPath(const QUrl &root, const QString &line) |
82 | { |
83 | // On Linux, backslashes are not converted to forward slashes :/ |
84 | if (line.startsWith(s: QLatin1String("//" )) || line.startsWith(s: QLatin1String("\\\\" ))) { |
85 | // Network share paths are not resolved |
86 | return QUrl::fromLocalFile(localfile: line); |
87 | } |
88 | |
89 | QUrl url(line); |
90 | if (url.scheme().isEmpty()) { |
91 | // Resolve it relative to root |
92 | if (root.isLocalFile()) |
93 | return QUrl::fromUserInput(userInput: line, workingDirectory: root.adjusted(options: QUrl::RemoveFilename).toLocalFile(), options: QUrl::AssumeLocalFile); |
94 | else |
95 | return root.resolved(relative: url); |
96 | } else if (url.scheme().length() == 1) { |
97 | // Assume it's a drive letter for a Windows path |
98 | url = QUrl::fromLocalFile(localfile: line); |
99 | } |
100 | |
101 | return url; |
102 | } |
103 | |
104 | void newItemFound(const QVariant& content) { Q_EMIT m_parent->newItem(content); } |
105 | |
106 | private: |
107 | QPlaylistFileParser *m_parent; |
108 | bool m_aborted; |
109 | }; |
110 | |
111 | class M3UParser : public ParserBase |
112 | { |
113 | public: |
114 | explicit M3UParser(QPlaylistFileParser *q) |
115 | : ParserBase(q) |
116 | , m_extendedFormat(false) |
117 | { |
118 | } |
119 | |
120 | /* |
121 | * |
122 | Extended M3U directives |
123 | |
124 | #EXTM3U - header - must be first line of file |
125 | #EXTINF - extra info - length (seconds), title |
126 | #EXTINF - extra info - length (seconds), artist '-' title |
127 | |
128 | Example |
129 | |
130 | #EXTM3U |
131 | #EXTINF:123, Sample artist - Sample title |
132 | C:\Documents and Settings\I\My Music\Sample.mp3 |
133 | #EXTINF:321,Example Artist - Example title |
134 | C:\Documents and Settings\I\My Music\Greatest Hits\Example.ogg |
135 | |
136 | */ |
137 | bool parseLineImpl(int lineIndex, const QString& line, const QUrl& root) override |
138 | { |
139 | if (line[0] == '#' ) { |
140 | if (m_extendedFormat) { |
141 | if (line.startsWith(s: QLatin1String("#EXTINF:" ))) { |
142 | m_extraInfo.clear(); |
143 | int artistStart = line.indexOf(s: QLatin1String("," ), from: 8); |
144 | bool ok = false; |
145 | int length = line.midRef(position: 8, n: artistStart < 8 ? -1 : artistStart - 8).trimmed().toInt(ok: &ok); |
146 | if (ok && length > 0) { |
147 | //convert from second to milisecond |
148 | m_extraInfo[QMediaMetaData::Duration] = QVariant(length * 1000); |
149 | } |
150 | if (artistStart > 0) { |
151 | int titleStart = getSplitIndex(line, startPos: artistStart); |
152 | if (titleStart > artistStart) { |
153 | m_extraInfo[QMediaMetaData::Author] = line.midRef(position: artistStart + 1, |
154 | n: titleStart - artistStart - 1).trimmed().toString(). |
155 | replace(before: QLatin1String("--" ), after: QLatin1String("-" )); |
156 | m_extraInfo[QMediaMetaData::Title] = line.midRef(position: titleStart + 1).trimmed().toString(). |
157 | replace(before: QLatin1String("--" ), after: QLatin1String("-" )); |
158 | } else { |
159 | m_extraInfo[QMediaMetaData::Title] = line.midRef(position: artistStart + 1).trimmed().toString(). |
160 | replace(before: QLatin1String("--" ), after: QLatin1String("-" )); |
161 | } |
162 | } |
163 | } |
164 | } else if (lineIndex == 0 && line.startsWith(s: QLatin1String("#EXTM3U" ))) { |
165 | m_extendedFormat = true; |
166 | } |
167 | } else { |
168 | m_extraInfo[QLatin1String("url" )] = expandToFullPath(root, line); |
169 | newItemFound(content: QVariant(m_extraInfo)); |
170 | m_extraInfo.clear(); |
171 | } |
172 | |
173 | return true; |
174 | } |
175 | |
176 | int getSplitIndex(const QString& line, int startPos) |
177 | { |
178 | if (startPos < 0) |
179 | startPos = 0; |
180 | const QChar* buf = line.data(); |
181 | for (int i = startPos; i < line.length(); ++i) { |
182 | if (buf[i] == '-') { |
183 | if (i == line.length() - 1) |
184 | return i; |
185 | ++i; |
186 | if (buf[i] != '-') |
187 | return i - 1; |
188 | } |
189 | } |
190 | return -1; |
191 | } |
192 | |
193 | private: |
194 | QVariantMap ; |
195 | bool m_extendedFormat; |
196 | }; |
197 | |
198 | class PLSParser : public ParserBase |
199 | { |
200 | public: |
201 | explicit PLSParser(QPlaylistFileParser *q) |
202 | : ParserBase(q) |
203 | { |
204 | } |
205 | |
206 | /* |
207 | * |
208 | The format is essentially that of an INI file structured as follows: |
209 | |
210 | Header |
211 | |
212 | * [playlist] : This tag indicates that it is a Playlist File |
213 | |
214 | Track Entry |
215 | Assuming track entry #X |
216 | |
217 | * FileX : Variable defining location of stream. |
218 | * TitleX : Defines track title. |
219 | * LengthX : Length in seconds of track. Value of -1 indicates indefinite. |
220 | |
221 | Footer |
222 | |
223 | * NumberOfEntries : This variable indicates the number of tracks. |
224 | * Version : Playlist version. Currently only a value of 2 is valid. |
225 | |
226 | [playlist] |
227 | |
228 | File1=Alternative\everclear - SMFTA.mp3 |
229 | |
230 | Title1=Everclear - So Much For The Afterglow |
231 | |
232 | Length1=233 |
233 | |
234 | File2=http://www.site.com:8000/listen.pls |
235 | |
236 | Title2=My Cool Stream |
237 | |
238 | Length5=-1 |
239 | |
240 | NumberOfEntries=2 |
241 | |
242 | Version=2 |
243 | */ |
244 | bool parseLineImpl(int, const QString &line, const QUrl &root) override |
245 | { |
246 | // We ignore everything but 'File' entries, since that's the only thing we care about. |
247 | if (!line.startsWith(s: QLatin1String("File" ))) |
248 | return true; |
249 | |
250 | QString value = getValue(line); |
251 | if (value.isEmpty()) |
252 | return true; |
253 | |
254 | newItemFound(content: expandToFullPath(root, line: value)); |
255 | |
256 | return true; |
257 | } |
258 | |
259 | QString getValue(const QString& line) { |
260 | int start = line.indexOf(c: '='); |
261 | if (start < 0) |
262 | return QString(); |
263 | return line.midRef(position: start + 1).trimmed().toString(); |
264 | } |
265 | }; |
266 | } |
267 | |
268 | ///////////////////////////////////////////////////////////////////////////////////////////////// |
269 | |
270 | class QPlaylistFileParserPrivate |
271 | { |
272 | Q_DECLARE_PUBLIC(QPlaylistFileParser) |
273 | public: |
274 | QPlaylistFileParserPrivate(QPlaylistFileParser *q) |
275 | : q_ptr(q) |
276 | , m_stream(nullptr) |
277 | , m_type(QPlaylistFileParser::UNKNOWN) |
278 | , m_scanIndex(0) |
279 | , m_lineIndex(-1) |
280 | , m_utf8(false) |
281 | , m_aborted(false) |
282 | { |
283 | } |
284 | |
285 | void handleData(); |
286 | void handleParserFinished(); |
287 | void abort(); |
288 | void reset(); |
289 | |
290 | QScopedPointer<QNetworkReply, QScopedPointerDeleteLater> m_source; |
291 | QScopedPointer<ParserBase> m_currentParser; |
292 | QByteArray m_buffer; |
293 | QUrl m_root; |
294 | QNetworkAccessManager m_mgr; |
295 | QString m_mimeType; |
296 | QPlaylistFileParser *q_ptr; |
297 | QIODevice *m_stream; |
298 | QPlaylistFileParser::FileType m_type; |
299 | struct ParserJob |
300 | { |
301 | QIODevice *m_stream; |
302 | QMediaContent m_media; |
303 | QString m_mimeType; |
304 | bool isValid() const { return m_stream || !m_media.isNull(); } |
305 | void reset() { m_stream = nullptr; m_media = QMediaContent(); m_mimeType = QString(); } |
306 | } m_pendingJob; |
307 | int m_scanIndex; |
308 | int m_lineIndex; |
309 | bool m_utf8; |
310 | bool m_aborted; |
311 | |
312 | private: |
313 | bool processLine(int startIndex, int length); |
314 | }; |
315 | |
316 | #define LINE_LIMIT 4096 |
317 | #define READ_LIMIT 64 |
318 | |
319 | bool QPlaylistFileParserPrivate::processLine(int startIndex, int length) |
320 | { |
321 | Q_Q(QPlaylistFileParser); |
322 | m_lineIndex++; |
323 | |
324 | if (!m_currentParser) { |
325 | const QString urlString = m_root.toString(); |
326 | const QString &suffix = !urlString.isEmpty() ? QFileInfo(urlString).suffix() : urlString; |
327 | const QString &mimeType = m_source->header(header: QNetworkRequest::ContentTypeHeader).toString(); |
328 | m_type = QPlaylistFileParser::findPlaylistType(suffix, mime: !mimeType.isEmpty() ? mimeType : m_mimeType, data: m_buffer.constData(), size: quint32(m_buffer.size())); |
329 | |
330 | switch (m_type) { |
331 | case QPlaylistFileParser::UNKNOWN: |
332 | emit q->error(err: QPlaylistFileParser::FormatError, |
333 | errorMsg: QPlaylistFileParser::tr(s: "%1 playlist type is unknown" ).arg(a: m_root.toString())); |
334 | q->abort(); |
335 | return false; |
336 | case QPlaylistFileParser::M3U: |
337 | m_currentParser.reset(other: new M3UParser(q)); |
338 | break; |
339 | case QPlaylistFileParser::M3U8: |
340 | m_currentParser.reset(other: new M3UParser(q)); |
341 | m_utf8 = true; |
342 | break; |
343 | case QPlaylistFileParser::PLS: |
344 | m_currentParser.reset(other: new PLSParser(q)); |
345 | break; |
346 | } |
347 | |
348 | Q_ASSERT(!m_currentParser.isNull()); |
349 | } |
350 | |
351 | QString line; |
352 | |
353 | if (m_utf8) { |
354 | line = QString::fromUtf8(str: m_buffer.constData() + startIndex, size: length).trimmed(); |
355 | } else { |
356 | line = QString::fromLatin1(str: m_buffer.constData() + startIndex, size: length).trimmed(); |
357 | } |
358 | if (line.isEmpty()) |
359 | return true; |
360 | |
361 | Q_ASSERT(m_currentParser); |
362 | return m_currentParser->parseLine(lineIndex: m_lineIndex, line, root: m_root); |
363 | } |
364 | |
365 | void QPlaylistFileParserPrivate::handleData() |
366 | { |
367 | Q_Q(QPlaylistFileParser); |
368 | while (m_source->bytesAvailable() && !m_aborted) { |
369 | int expectedBytes = qMin(READ_LIMIT, b: int(qMin(a: m_source->bytesAvailable(), |
370 | b: qint64(LINE_LIMIT - m_buffer.size())))); |
371 | m_buffer.push_back(a: m_source->read(maxlen: expectedBytes)); |
372 | int processedBytes = 0; |
373 | while (m_scanIndex < m_buffer.length() && !m_aborted) { |
374 | char s = m_buffer[m_scanIndex]; |
375 | if (s == '\r' || s == '\n') { |
376 | int l = m_scanIndex - processedBytes; |
377 | if (l > 0) { |
378 | if (!processLine(startIndex: processedBytes, length: l)) |
379 | break; |
380 | } |
381 | processedBytes = m_scanIndex + 1; |
382 | if (!m_source) { |
383 | //some error happened, so exit parsing |
384 | return; |
385 | } |
386 | } |
387 | m_scanIndex++; |
388 | } |
389 | |
390 | if (m_aborted) |
391 | break; |
392 | |
393 | if (m_buffer.length() - processedBytes >= LINE_LIMIT) { |
394 | emit q->error(err: QPlaylistFileParser::FormatError, errorMsg: QPlaylistFileParser::tr(s: "invalid line in playlist file" )); |
395 | q->abort(); |
396 | break; |
397 | } |
398 | |
399 | if (m_source->isFinished() && !m_source->bytesAvailable()) { |
400 | //last line |
401 | processLine(startIndex: processedBytes, length: -1); |
402 | break; |
403 | } |
404 | |
405 | Q_ASSERT(m_buffer.length() == m_scanIndex); |
406 | if (processedBytes == 0) |
407 | continue; |
408 | |
409 | int copyLength = m_buffer.length() - processedBytes; |
410 | if (copyLength > 0) { |
411 | Q_ASSERT(copyLength <= READ_LIMIT); |
412 | m_buffer = m_buffer.right(len: copyLength); |
413 | } else { |
414 | m_buffer.clear(); |
415 | } |
416 | m_scanIndex = 0; |
417 | } |
418 | |
419 | handleParserFinished(); |
420 | } |
421 | |
422 | QPlaylistFileParser::QPlaylistFileParser(QObject *parent) |
423 | : QObject(parent) |
424 | , d_ptr(new QPlaylistFileParserPrivate(this)) |
425 | { |
426 | |
427 | } |
428 | |
429 | QPlaylistFileParser::~QPlaylistFileParser() |
430 | { |
431 | |
432 | } |
433 | |
434 | QPlaylistFileParser::FileType QPlaylistFileParser::findByMimeType(const QString &mime) |
435 | { |
436 | if (mime == QLatin1String("text/uri-list" ) || mime == QLatin1String("audio/x-mpegurl" ) || mime == QLatin1String("audio/mpegurl" )) |
437 | return QPlaylistFileParser::M3U; |
438 | |
439 | if (mime == QLatin1String("application/x-mpegURL" ) || mime == QLatin1String("application/vnd.apple.mpegurl" )) |
440 | return QPlaylistFileParser::M3U8; |
441 | |
442 | if (mime == QLatin1String("audio/x-scpls" )) |
443 | return QPlaylistFileParser::PLS; |
444 | |
445 | return QPlaylistFileParser::UNKNOWN; |
446 | } |
447 | |
448 | QPlaylistFileParser::FileType QPlaylistFileParser::findBySuffixType(const QString &suffix) |
449 | { |
450 | const QString &s = suffix.toLower(); |
451 | |
452 | if (s == QLatin1String("m3u" )) |
453 | return QPlaylistFileParser::M3U; |
454 | |
455 | if (s == QLatin1String("m3u8" )) |
456 | return QPlaylistFileParser::M3U8; |
457 | |
458 | if (s == QLatin1String("pls" )) |
459 | return QPlaylistFileParser::PLS; |
460 | |
461 | return QPlaylistFileParser::UNKNOWN; |
462 | } |
463 | |
464 | QPlaylistFileParser::FileType QPlaylistFileParser::(const char *data, quint32 size) |
465 | { |
466 | if (!data || size == 0) |
467 | return QPlaylistFileParser::UNKNOWN; |
468 | |
469 | if (size >= 7 && strncmp(s1: data, s2: "#EXTM3U" , n: 7) == 0) |
470 | return QPlaylistFileParser::M3U; |
471 | |
472 | if (size >= 10 && strncmp(s1: data, s2: "[playlist]" , n: 10) == 0) |
473 | return QPlaylistFileParser::PLS; |
474 | |
475 | return QPlaylistFileParser::UNKNOWN; |
476 | } |
477 | |
478 | QPlaylistFileParser::FileType QPlaylistFileParser::findPlaylistType(const QString& suffix, |
479 | const QString& mime, |
480 | const char *data, |
481 | quint32 size) |
482 | { |
483 | |
484 | FileType = findByDataHeader(data, size); |
485 | if (dataHeaderType != UNKNOWN) |
486 | return dataHeaderType; |
487 | |
488 | FileType mimeType = findByMimeType(mime); |
489 | if (mimeType != UNKNOWN) |
490 | return mimeType; |
491 | |
492 | FileType suffixType = findBySuffixType(suffix); |
493 | if (suffixType != UNKNOWN) |
494 | return suffixType; |
495 | |
496 | return UNKNOWN; |
497 | } |
498 | |
499 | /* |
500 | * Delegating |
501 | */ |
502 | void QPlaylistFileParser::start(const QMediaContent &media, QIODevice *stream, const QString &mimeType) |
503 | { |
504 | if (stream) |
505 | start(stream, mimeType); |
506 | else |
507 | start(request: media.request(), mimeType); |
508 | } |
509 | |
510 | void QPlaylistFileParser::start(QIODevice *stream, const QString &mimeType) |
511 | { |
512 | Q_D(QPlaylistFileParser); |
513 | const bool validStream = stream ? (stream->isOpen() && stream->isReadable()) : false; |
514 | |
515 | if (!validStream) { |
516 | Q_EMIT error(err: ResourceError, errorMsg: tr(s: "Invalid stream" )); |
517 | return; |
518 | } |
519 | |
520 | if (!d->m_currentParser.isNull()) { |
521 | abort(); |
522 | d->m_pendingJob = { .m_stream: stream, .m_media: QUrl(), .m_mimeType: mimeType }; |
523 | return; |
524 | } |
525 | |
526 | d->reset(); |
527 | d->m_mimeType = mimeType; |
528 | d->m_stream = stream; |
529 | connect(sender: d->m_stream, SIGNAL(readyRead()), receiver: this, SLOT(_q_handleData())); |
530 | d->handleData(); |
531 | } |
532 | |
533 | void QPlaylistFileParser::start(const QNetworkRequest& request, const QString &mimeType) |
534 | { |
535 | Q_D(QPlaylistFileParser); |
536 | const QUrl &url = request.url(); |
537 | |
538 | if (url.isLocalFile() && !QFile::exists(fileName: url.toLocalFile())) { |
539 | emit error(err: ResourceError, errorMsg: QString(tr(s: "%1 does not exist" )).arg(a: url.toString())); |
540 | return; |
541 | } |
542 | |
543 | if (!d->m_currentParser.isNull()) { |
544 | abort(); |
545 | d->m_pendingJob = { .m_stream: nullptr, .m_media: request, .m_mimeType: mimeType }; |
546 | return; |
547 | } |
548 | |
549 | d->reset(); |
550 | d->m_root = url; |
551 | d->m_mimeType = mimeType; |
552 | d->m_source.reset(other: d->m_mgr.get(request)); |
553 | connect(sender: d->m_source.data(), SIGNAL(readyRead()), receiver: this, SLOT(handleData())); |
554 | connect(sender: d->m_source.data(), SIGNAL(finished()), receiver: this, SLOT(handleData())); |
555 | connect(sender: d->m_source.data(), SIGNAL(errorOccurred(QNetworkReply::NetworkError)), receiver: this, SLOT(handleError())); |
556 | |
557 | if (url.isLocalFile()) |
558 | d->handleData(); |
559 | } |
560 | |
561 | void QPlaylistFileParser::abort() |
562 | { |
563 | Q_D(QPlaylistFileParser); |
564 | d->abort(); |
565 | |
566 | if (d->m_source) |
567 | d->m_source->disconnect(); |
568 | |
569 | if (d->m_stream) |
570 | disconnect(sender: d->m_stream, SIGNAL(readyRead()), receiver: this, SLOT(handleData())); |
571 | } |
572 | |
573 | void QPlaylistFileParser::handleData() |
574 | { |
575 | Q_D(QPlaylistFileParser); |
576 | d->handleData(); |
577 | } |
578 | |
579 | void QPlaylistFileParserPrivate::handleParserFinished() |
580 | { |
581 | Q_Q(QPlaylistFileParser); |
582 | const bool isParserValid = !m_currentParser.isNull(); |
583 | if (!isParserValid && !m_aborted) |
584 | emit q->error(err: QPlaylistFileParser::FormatNotSupportedError, errorMsg: QPlaylistFileParser::tr(s: "Empty file provided" )); |
585 | |
586 | if (isParserValid && !m_aborted) { |
587 | m_currentParser.reset(); |
588 | emit q->finished(); |
589 | } |
590 | |
591 | if (!m_aborted) |
592 | q->abort(); |
593 | |
594 | if (!m_source.isNull()) |
595 | m_source.reset(); |
596 | |
597 | if (m_pendingJob.isValid()) |
598 | q->start(media: m_pendingJob.m_media, stream: m_pendingJob.m_stream, mimeType: m_pendingJob.m_mimeType); |
599 | } |
600 | |
601 | void QPlaylistFileParserPrivate::abort() |
602 | { |
603 | m_aborted = true; |
604 | if (!m_currentParser.isNull()) |
605 | m_currentParser->abort(); |
606 | } |
607 | |
608 | void QPlaylistFileParserPrivate::reset() |
609 | { |
610 | Q_ASSERT(m_currentParser.isNull()); |
611 | Q_ASSERT(m_source.isNull()); |
612 | m_buffer.clear(); |
613 | m_root.clear(); |
614 | m_mimeType.clear(); |
615 | m_stream = 0; |
616 | m_type = QPlaylistFileParser::UNKNOWN; |
617 | m_scanIndex = 0; |
618 | m_lineIndex = -1; |
619 | m_utf8 = false; |
620 | m_aborted = false; |
621 | m_pendingJob.reset(); |
622 | } |
623 | |
624 | void QPlaylistFileParser::handleError() |
625 | { |
626 | Q_D(QPlaylistFileParser); |
627 | const QString &errorString = d->m_source->errorString(); |
628 | Q_EMIT error(err: QPlaylistFileParser::NetworkError, errorMsg: errorString); |
629 | abort(); |
630 | } |
631 | |
632 | QT_END_NAMESPACE |
633 | |