diff --git a/Telegram/SourceFiles/art/iconbig128.png b/Telegram/SourceFiles/art/iconbig128.png new file mode 100644 index 000000000..32b44a5ea Binary files /dev/null and b/Telegram/SourceFiles/art/iconbig128.png differ diff --git a/Telegram/SourceFiles/history.cpp b/Telegram/SourceFiles/history.cpp index d89bd59ea..bcb433e63 100644 --- a/Telegram/SourceFiles/history.cpp +++ b/Telegram/SourceFiles/history.cpp @@ -1755,7 +1755,7 @@ void History::clear(bool leaveItems) { _overview[i].clear(); _overviewIds[i].clear(); } - if (App::wnd()) App::wnd()->mediaOverviewUpdated(peer); + if (App::wnd() && !App::quiting()) App::wnd()->mediaOverviewUpdated(peer); for (Parent::const_iterator i = cbegin(), e = cend(); i != e; ++i) { if (leaveItems) { (*i)->clear(true); diff --git a/Telegram/SourceFiles/mtproto/mtp.h b/Telegram/SourceFiles/mtproto/mtp.h index 409379863..0ac3edef9 100644 --- a/Telegram/SourceFiles/mtproto/mtp.h +++ b/Telegram/SourceFiles/mtproto/mtp.h @@ -95,7 +95,10 @@ namespace MTP { void initdc(int32 dc); template inline mtpRequestId send(const TRequest &request, RPCResponseHandler callbacks = RPCResponseHandler(), int32 dc = 0, uint64 msCanWait = 0, mtpRequestId after = 0) { - return _mtp_internal::getSession(dc)->send(request, callbacks, msCanWait, _mtp_internal::getLayer(), !dc, after); + MTProtoSessionPtr session = _mtp_internal::getSession(dc); + if (!session) return 0; + + return session->send(request, callbacks, msCanWait, _mtp_internal::getLayer(), !dc, after); } template inline mtpRequestId send(const TRequest &request, RPCDoneHandlerPtr onDone, RPCFailHandlerPtr onFail = RPCFailHandlerPtr(), int32 dc = 0, uint64 msCanWait = 0, mtpRequestId after = 0) { diff --git a/Telegram/SourceFiles/mtproto/mtpFileLoader.cpp b/Telegram/SourceFiles/mtproto/mtpFileLoader.cpp index 28bd479f6..75dca6ed9 100644 --- a/Telegram/SourceFiles/mtproto/mtpFileLoader.cpp +++ b/Telegram/SourceFiles/mtproto/mtpFileLoader.cpp @@ -46,7 +46,7 @@ namespace { mtpFileLoader::mtpFileLoader(int32 dc, const int64 &volume, int32 local, const int64 &secret, int32 size) : prev(0), next(0), priority(0), inQueue(false), complete(false), skippedBytes(0), nextRequestOffset(0), lastComplete(false), dc(dc), locationType(0), volume(volume), local(local), secret(secret), -id(0), access(0), size(size), type(MTP_storage_fileUnknown()) { +id(0), access(0), fileIsOpen(false), size(size), type(MTP_storage_fileUnknown()) { LoaderQueues::iterator i = queues.find(dc); if (i == queues.cend()) { i = queues.insert(dc, mtpFileLoaderQueue()); @@ -57,7 +57,7 @@ id(0), access(0), size(size), type(MTP_storage_fileUnknown()) { mtpFileLoader::mtpFileLoader(int32 dc, const uint64 &id, const uint64 &access, mtpTypeId locType, const QString &to, int32 size) : prev(0), next(0), priority(0), inQueue(false), complete(false), skippedBytes(0), nextRequestOffset(0), lastComplete(false), dc(dc), locationType(locType), -id(id), access(access), file(to), duplicateInData(false), size(size), type(MTP_storage_fileUnknown()) { +id(id), access(access), file(to), fname(to), fileIsOpen(false), duplicateInData(false), size(size), type(MTP_storage_fileUnknown()) { LoaderQueues::iterator i = queues.find(MTP::dld[0] + dc); if (i == queues.cend()) { i = queues.insert(MTP::dld[0] + dc, mtpFileLoaderQueue()); @@ -68,7 +68,7 @@ id(id), access(access), file(to), duplicateInData(false), size(size), type(MTP_s mtpFileLoader::mtpFileLoader(int32 dc, const uint64 &id, const uint64 &access, mtpTypeId locType, const QString &to, int32 size, bool todata) : prev(0), next(0), priority(0), inQueue(false), complete(false), skippedBytes(0), nextRequestOffset(0), lastComplete(false), dc(dc), locationType(locType), -id(id), access(access), file(to), duplicateInData(todata), size(size), type(MTP_storage_fileUnknown()) { +id(id), access(access), file(to), fname(to), fileIsOpen(false), duplicateInData(todata), size(size), type(MTP_storage_fileUnknown()) { LoaderQueues::iterator i = queues.find(MTP::dld[0] + dc); if (i == queues.cend()) { i = queues.insert(MTP::dld[0] + dc, mtpFileLoaderQueue()); @@ -77,7 +77,7 @@ id(id), access(access), file(to), duplicateInData(todata), size(size), type(MTP_ } QString mtpFileLoader::fileName() const { - return file.fileName(); + return fname; } bool mtpFileLoader::done() const { @@ -99,16 +99,16 @@ float64 mtpFileLoader::currentProgress() const { } int32 mtpFileLoader::currentOffset(bool includeSkipped) const { - return (file.isOpen() ? file.size() : data.size()) - (includeSkipped ? 0 : skippedBytes); + return (fileIsOpen ? file.size() : data.size()) - (includeSkipped ? 0 : skippedBytes); } int32 mtpFileLoader::fullSize() const { return size; } -void mtpFileLoader::setFileName(const QString &fname) { - if (duplicateInData && file.fileName().isEmpty()) { - file.setFileName(fname); +void mtpFileLoader::setFileName(const QString &fileName) { + if (duplicateInData && fname.isEmpty()) { + file.setFileName(fname = fileName); } } @@ -132,13 +132,14 @@ void mtpFileLoader::finishFail() { cancelRequests(); type = MTP_storage_fileUnknown(); complete = true; - if (file.isOpen()) { + if (fileIsOpen) { file.close(); + fileIsOpen = false; file.remove(); } data = QByteArray(); emit failed(this, started); - file.setFileName(QString()); + file.setFileName(fname = QString()); loadNext(); } @@ -198,7 +199,7 @@ void mtpFileLoader::partLoaded(int32 offset, const MTPupload_File &result, mtpRe const MTPDupload_file &d(result.c_upload_file()); const string &bytes(d.vbytes.c_string().v); if (bytes.size()) { - if (file.isOpen()) { + if (fileIsOpen) { int64 fsize = file.size(); if (offset < fsize) { skippedBytes -= bytes.size(); @@ -230,8 +231,9 @@ void mtpFileLoader::partLoaded(int32 offset, const MTPupload_File &result, mtpRe lastComplete = true; } if (requests.isEmpty() && (lastComplete || (size && nextRequestOffset >= size))) { - if (duplicateInData && !file.fileName().isEmpty()) { - if (!file.open(QIODevice::WriteOnly)) { + if (!fname.isEmpty() && duplicateInData) { + if (!fileIsOpen) fileIsOpen = file.open(QIODevice::WriteOnly); + if (!fileIsOpen) { return finishFail(); } if (file.write(data) != qint64(data.size())) { @@ -240,8 +242,9 @@ void mtpFileLoader::partLoaded(int32 offset, const MTPupload_File &result, mtpRe } type = d.vtype; complete = true; - if (file.isOpen()) { + if (fileIsOpen) { file.close(); + fileIsOpen = false; psPostprocessFile(QFileInfo(file).absoluteFilePath()); } removeFromQueue(); @@ -286,8 +289,9 @@ void mtpFileLoader::pause() { void mtpFileLoader::start(bool loadFirst, bool prior) { if (complete) return; - if (!file.fileName().isEmpty() && !duplicateInData) { - if (!file.open(QIODevice::WriteOnly)) { + if (!fname.isEmpty() && !duplicateInData && !fileIsOpen) { + fileIsOpen = file.open(QIODevice::WriteOnly); + if (!fileIsOpen) { finishFail(); return; } @@ -385,8 +389,9 @@ void mtpFileLoader::cancel() { cancelRequests(); type = MTP_storage_fileUnknown(); complete = true; - if (file.isOpen()) { + if (fileIsOpen) { file.close(); + fileIsOpen = false; file.remove(); } data = QByteArray(); diff --git a/Telegram/SourceFiles/mtproto/mtpFileLoader.h b/Telegram/SourceFiles/mtproto/mtpFileLoader.h index 287fd67f9..99781f694 100644 --- a/Telegram/SourceFiles/mtproto/mtpFileLoader.h +++ b/Telegram/SourceFiles/mtproto/mtpFileLoader.h @@ -89,6 +89,8 @@ private: uint64 id; // for other locations uint64 access; QFile file; + QString fname; + bool fileIsOpen; bool duplicateInData; QByteArray data; diff --git a/Telegram/SourceFiles/overviewwidget.cpp b/Telegram/SourceFiles/overviewwidget.cpp index 4deb12b3b..76d1ab470 100644 --- a/Telegram/SourceFiles/overviewwidget.cpp +++ b/Telegram/SourceFiles/overviewwidget.cpp @@ -553,10 +553,14 @@ QPixmap OverviewInner::genPix(PhotoData *photo, int32 size) { if (!photo->full->loaded() && !photo->medium->loaded()) { img = imageBlur(img); } - if (img.width() > img.height()) { - img = img.scaled(img.width() * size / img.height(), size, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation); + if (img.width() == img.height()) { + if (img.width() != size) { + img = img.scaled(size, size, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation); + } + } else if (img.width() > img.height()) { + img = img.copy((img.width() - img.height()) / 2, 0, img.height(), img.height()).scaled(size, size, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation); } else { - img = img.scaled(size, img.height() * size / img.width(), Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation); + img = img.copy(0, (img.height() - img.width()) / 2, img.width(), img.width()).scaled(size, size, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation); } img.setDevicePixelRatio(cRetinaFactor()); photo->forget(); @@ -625,26 +629,13 @@ void OverviewInner::paintEvent(QPaintEvent *e) { it->vsize = _vsize; it->pix = genPix(photo, _vsize); } - QPixmap &pix(it->pix); QPoint pos(int32(i * w + st::overviewPhotoSkip), _addToY + row * (_vsize + st::overviewPhotoSkip) + st::overviewPhotoSkip); - int32 w = pix.width(), h = pix.height(), size; - if (w == h) { - p.drawPixmap(pos, pix); - size = w; - } else if (w > h) { - p.drawPixmap(pos, pix, QRect((w - h) / 2, 0, h, h)); - size = h; - } else { - p.drawPixmap(pos, pix, QRect(0, (h - w) / 2, w, w)); - size = w; - } - size /= cIntRetinaFactor(); - + p.drawPixmap(pos, it->pix); if (!quality) { uint64 dt = itemAnimations().animate(item, getms()); int32 cnt = int32(st::photoLoaderCnt), period = int32(st::photoLoaderPeriod), t = dt % period, delta = int32(st::photoLoaderDelta); - int32 x = pos.x() + (size - st::overviewLoader.width()) / 2, y = pos.y() + (size - st::overviewLoader.height()) / 2; + int32 x = pos.x() + (_vsize - st::overviewLoader.width()) / 2, y = pos.y() + (_vsize - st::overviewLoader.height()) / 2; p.fillRect(x, y, st::overviewLoader.width(), st::overviewLoader.height(), st::photoLoaderBg->b); x += (st::overviewLoader.width() - cnt * st::overviewLoaderPoint.width() - (cnt - 1) * st::overviewLoaderSkip) / 2; y += (st::overviewLoader.height() - st::overviewLoaderPoint.height()) / 2; @@ -671,7 +662,7 @@ void OverviewInner::paintEvent(QPaintEvent *e) { } } if (sel == FullItemSel) { - p.fillRect(QRect(pos.x(), pos.y(), size, size), st::msgInSelectOverlay->b); + p.fillRect(QRect(pos.x(), pos.y(), _vsize, _vsize), st::msgInSelectOverlay->b); } } break; } diff --git a/Telegram/SourceFiles/pspecific_linux.cpp b/Telegram/SourceFiles/pspecific_linux.cpp index 09d14813e..3da05918c 100644 --- a/Telegram/SourceFiles/pspecific_linux.cpp +++ b/Telegram/SourceFiles/pspecific_linux.cpp @@ -50,7 +50,7 @@ namespace { }; PsMainWindow::PsMainWindow(QWidget *parent) : QMainWindow(parent), -posInited(false), trayIcon(0), trayIconMenu(0), icon256(qsl(":/gui/art/iconround256.png")) { +posInited(false), trayIcon(0), trayIconMenu(0), icon256(qsl(":/gui/art/iconround256.png")), wndIcon(QPixmap::fromImage(icon256)) { connect(&psIdleTimer, SIGNAL(timeout()), this, SLOT(psIdleTimeout())); psIdleTimer.setSingleShot(false); } @@ -115,6 +115,8 @@ void PsMainWindow::psUpdateWorkmode() { } void PsMainWindow::psUpdateCounter() { + setWindowIcon(myIcon); + int32 counter = App::histories().unreadFull; setWindowTitle((counter > 0) ? qsl("Telegram (%1)").arg(counter) : qsl("Telegram")); diff --git a/Telegram/SourceFiles/pspecific_linux.h b/Telegram/SourceFiles/pspecific_linux.h index 7fba2bb32..7dcd62e2e 100644 --- a/Telegram/SourceFiles/pspecific_linux.h +++ b/Telegram/SourceFiles/pspecific_linux.h @@ -88,6 +88,7 @@ protected: QSystemTrayIcon *trayIcon; QMenu *trayIconMenu; QImage icon256; + QIcon wndIcon; virtual void setupTrayIcon() = 0; virtual QImage iconWithCounter(int size, int count, style::color bg, bool smallIcon) = 0; diff --git a/Telegram/SourceFiles/pspecific_mac.cpp b/Telegram/SourceFiles/pspecific_mac.cpp index b76e618c4..f9565882c 100644 --- a/Telegram/SourceFiles/pspecific_mac.cpp +++ b/Telegram/SourceFiles/pspecific_mac.cpp @@ -64,7 +64,7 @@ void MacPrivate::notifyReplied(unsigned long long peer, const char *str) { } PsMainWindow::PsMainWindow(QWidget *parent) : QMainWindow(parent), -posInited(false), trayIcon(0), trayIconMenu(0), icon256(qsl(":/gui/art/iconround256.png")) { +posInited(false), trayIcon(0), trayIconMenu(0), icon256(qsl(":/gui/art/iconround256.png")), wndIcon(QPixmap(qsl(":/gui/art/iconbig128.png"))) { QImage tray(qsl(":/gui/art/osxtray.png")); trayImg = tray.copy(0, cRetina() ? 0 : tray.width() / 2, tray.width() / (cRetina() ? 2 : 4), tray.width() / (cRetina() ? 2 : 4)); trayImgSel = tray.copy(tray.width() / (cRetina() ? 2 : 4), cRetina() ? 0 : tray.width() / 2, tray.width() / (cRetina() ? 2 : 4), tray.width() / (cRetina() ? 2 : 4)); @@ -141,6 +141,7 @@ void PsMainWindow::psUpdateWorkmode() { } trayIcon = 0; } + setWindowIcon(wndIcon); } void _placeCounter(QImage &img, int size, int count, style::color bg, style::color color) { @@ -182,6 +183,7 @@ void PsMainWindow::psUpdateCounter() { int32 counter = App::histories().unreadFull; setWindowTitle((counter > 0) ? qsl("Telegram (%1)").arg(counter) : qsl("Telegram")); + setWindowIcon(wndIcon); QString cnt = (counter < 1000) ? QString("%1").arg(counter) : QString("..%1").arg(counter % 100, 2, 10, QChar('0')); _private.setWindowBadge(counter ? cnt : QString()); diff --git a/Telegram/SourceFiles/pspecific_mac.h b/Telegram/SourceFiles/pspecific_mac.h index f05715984..cb8e971a0 100644 --- a/Telegram/SourceFiles/pspecific_mac.h +++ b/Telegram/SourceFiles/pspecific_mac.h @@ -99,6 +99,7 @@ protected: QSystemTrayIcon *trayIcon; QMenu *trayIconMenu; QImage icon256; + QIcon wndIcon; QImage trayImg, trayImgSel; diff --git a/Telegram/SourceFiles/pspecific_wnd.cpp b/Telegram/SourceFiles/pspecific_wnd.cpp index 57e6458e8..0f8fa9eca 100644 --- a/Telegram/SourceFiles/pspecific_wnd.cpp +++ b/Telegram/SourceFiles/pspecific_wnd.cpp @@ -860,7 +860,7 @@ namespace { }; -PsMainWindow::PsMainWindow(QWidget *parent) : QMainWindow(parent), ps_hWnd(0), ps_menu(0), icon256(qsl(":/gui/art/iconround256.png")), +PsMainWindow::PsMainWindow(QWidget *parent) : QMainWindow(parent), ps_hWnd(0), ps_menu(0), icon256(qsl(":/gui/art/iconround256.png")), wndIcon(QPixmap::fromImage(icon256)), ps_iconBig(0), ps_iconSmall(0), ps_iconOverlay(0), trayIcon(0), trayIconMenu(0), posInited(false), ps_tbHider_hWnd(createTaskbarHider()), psIdle(false) { tbCreatedMsgId = RegisterWindowMessage(L"TaskbarButtonCreated"); connect(&psIdleTimer, SIGNAL(timeout()), this, SLOT(psIdleTimeout())); diff --git a/Telegram/SourceFiles/pspecific_wnd.h b/Telegram/SourceFiles/pspecific_wnd.h index ecfcaf50b..94b4f4f15 100644 --- a/Telegram/SourceFiles/pspecific_wnd.h +++ b/Telegram/SourceFiles/pspecific_wnd.h @@ -87,6 +87,7 @@ protected: QSystemTrayIcon *trayIcon; ContextMenu *trayIconMenu; QImage icon256; + QIcon wndIcon; virtual void setupTrayIcon() = 0; virtual QImage iconWithCounter(int size, int count, style::color bg, bool smallIcon) = 0; diff --git a/Telegram/SourceFiles/telegram.qrc b/Telegram/SourceFiles/telegram.qrc index 93525b044..8bc2e1f1b 100644 --- a/Telegram/SourceFiles/telegram.qrc +++ b/Telegram/SourceFiles/telegram.qrc @@ -18,6 +18,7 @@ art/emoji_200x.png art/blank.gif art/iconround256.png + art/iconbig128.png art/fonts/DejaVuSans.ttf art/osxtray.png diff --git a/Telegram/SourceFiles/window.cpp b/Telegram/SourceFiles/window.cpp index 9fd601335..fb78629c0 100644 --- a/Telegram/SourceFiles/window.cpp +++ b/Telegram/SourceFiles/window.cpp @@ -337,7 +337,7 @@ NotifyWindow::~NotifyWindow() { Window::Window(QWidget *parent) : PsMainWindow(parent), intro(0), main(0), settings(0), layerBG(0), _topWidget(0), -_connecting(0), _tempDeleter(0), _tempDeleterThread(0), myIcon(QPixmap::fromImage(icon256)), dragging(false), _inactivePress(false), _mediaView(0) { +_connecting(0), _tempDeleter(0), _tempDeleterThread(0), dragging(false), _inactivePress(false), _mediaView(0) { icon16 = icon256.scaledToWidth(16, Qt::SmoothTransformation); icon32 = icon256.scaledToWidth(32, Qt::SmoothTransformation); @@ -380,7 +380,7 @@ void Window::onInactiveTimer() { void Window::init() { psInitFrameless(); - setWindowIcon(myIcon); + setWindowIcon(wndIcon); App::app()->installEventFilter(this); connect(windowHandle(), SIGNAL(activeChanged()), this, SLOT(checkHistoryActivation())); @@ -875,7 +875,6 @@ void Window::noTopWidget(QWidget *w) { void Window::showFromTray(QSystemTrayIcon::ActivationReason reason) { if (reason != QSystemTrayIcon::Context) { activate(); - setWindowIcon(myIcon); psUpdateCounter(); if (App::main()) App::main()->setOnline(windowState()); QTimer::singleShot(1, this, SLOT(updateTrayMenu())); diff --git a/Telegram/SourceFiles/window.h b/Telegram/SourceFiles/window.h index 18bcb44a6..15b2ea0b5 100644 --- a/Telegram/SourceFiles/window.h +++ b/Telegram/SourceFiles/window.h @@ -281,8 +281,6 @@ private: void clearWidgets(); - QIcon myIcon; - bool dragging; QPoint dragStart; diff --git a/Telegram/_qt_5_3_1_patch/qtbase/src/plugins/platforms/cocoa/qcocoawindow.mm b/Telegram/_qt_5_3_1_patch/qtbase/src/plugins/platforms/cocoa/qcocoawindow.mm index f3033316f..159395aa2 100644 --- a/Telegram/_qt_5_3_1_patch/qtbase/src/plugins/platforms/cocoa/qcocoawindow.mm +++ b/Telegram/_qt_5_3_1_patch/qtbase/src/plugins/platforms/cocoa/qcocoawindow.mm @@ -167,7 +167,7 @@ static bool isMouseEvent(NSEvent *ev) if (!self.window.delegate) return; // Already detached, pending NSAppKitDefined event - if (pw && pw->frameStrutEventsEnabled() && pw->m_synchedWindowState != Qt::WindowMinimized && isMouseEvent(theEvent)) { + if (pw && pw->frameStrutEventsEnabled() && pw->m_synchedWindowState != Qt::WindowMinimized && pw->m_isExposed && isMouseEvent(theEvent)) { NSPoint loc = [theEvent locationInWindow]; NSRect windowFrame = [self.window legacyConvertRectFromScreen:[self.window frame]]; NSRect contentFrame = [[self.window contentView] frame]; @@ -903,6 +903,14 @@ void QCocoaWindow::setWindowFilePath(const QString &filePath) [m_nsWindow setRepresentedFilename: fi.exists() ? QCFString::toNSString(filePath) : @""]; } +qreal _win_devicePixelRatio() { + qreal result = 1.0; + foreach (QScreen *screen, QGuiApplication::screens()) { + result = qMax(result, screen->devicePixelRatio()); + } + return result; +} + void QCocoaWindow::setWindowIcon(const QIcon &icon) { QCocoaAutoReleasePool pool; @@ -918,7 +926,8 @@ void QCocoaWindow::setWindowIcon(const QIcon &icon) if (icon.isNull()) { [iconButton setImage:nil]; } else { - QPixmap pixmap = icon.pixmap(QSize(22, 22)); + CGFloat hgt = 16. * _win_devicePixelRatio(); + QPixmap pixmap = icon.pixmap(QSize(hgt, hgt)); NSImage *image = static_cast(qt_mac_create_nsimage(pixmap)); [iconButton setImage:image]; [image release]; diff --git a/Telegram/_qt_5_3_1_patch/qtmultimedia/src/plugins/avfoundation/mediaplayer/avfmediaplayersession.mm b/Telegram/_qt_5_3_1_patch/qtmultimedia/src/plugins/avfoundation/mediaplayer/avfmediaplayersession.mm new file mode 100644 index 000000000..1b7b863f9 --- /dev/null +++ b/Telegram/_qt_5_3_1_patch/qtmultimedia/src/plugins/avfoundation/mediaplayer/avfmediaplayersession.mm @@ -0,0 +1,851 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "avfmediaplayersession.h" +#include "avfmediaplayerservice.h" +#include "avfvideooutput.h" + +#import + +QT_USE_NAMESPACE + +//AVAsset Keys +static NSString* const AVF_TRACKS_KEY = @"tracks"; +static NSString* const AVF_PLAYABLE_KEY = @"playable"; + +//AVPlayerItem keys +static NSString* const AVF_STATUS_KEY = @"status"; + +//AVPlayer keys +static NSString* const AVF_RATE_KEY = @"rate"; +static NSString* const AVF_CURRENT_ITEM_KEY = @"currentItem"; + +static void *AVFMediaPlayerSessionObserverRateObservationContext = &AVFMediaPlayerSessionObserverRateObservationContext; +static void *AVFMediaPlayerSessionObserverStatusObservationContext = &AVFMediaPlayerSessionObserverStatusObservationContext; +static void *AVFMediaPlayerSessionObserverCurrentItemObservationContext = &AVFMediaPlayerSessionObserverCurrentItemObservationContext; + +@interface AVFMediaPlayerSessionObserver : NSObject +{ +@private + AVFMediaPlayerSession *m_session; + AVPlayer *m_player; + AVPlayerItem *m_playerItem; + AVPlayerLayer *m_playerLayer; + NSURL *m_URL; + bool m_audioAvailable; + bool m_videoAvailable; +} + +@property (readonly, getter=player) AVPlayer* m_player; +@property (readonly, getter=playerItem) AVPlayerItem* m_playerItem; +@property (readonly, getter=playerLayer) AVPlayerLayer* m_playerLayer; +@property (readonly, getter=audioAvailable) bool m_audioAvailable; +@property (readonly, getter=videoAvailable) bool m_videoAvailable; +@property (readonly, getter=session) AVFMediaPlayerSession* m_session; + +- (AVFMediaPlayerSessionObserver *) initWithMediaPlayerSession:(AVFMediaPlayerSession *)session; +- (void) setURL:(NSURL *)url; +- (void) unloadMedia; +- (void) prepareToPlayAsset:(AVURLAsset *)asset withKeys:(NSArray *)requestedKeys; +- (void) assetFailedToPrepareForPlayback:(NSError *)error; +- (void) playerItemDidReachEnd:(NSNotification *)notification; +- (void) playerItemTimeJumped:(NSNotification *)notification; +- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object + change:(NSDictionary *)change context:(void *)context; +- (void) detatchSession; +- (void) dealloc; +@end + +@implementation AVFMediaPlayerSessionObserver + +@synthesize m_player, m_playerItem, m_playerLayer, m_audioAvailable, m_videoAvailable, m_session; + +- (AVFMediaPlayerSessionObserver *) initWithMediaPlayerSession:(AVFMediaPlayerSession *)session +{ + if (!(self = [super init])) + return nil; + + self->m_session = session; + return self; +} + +- (void) setURL:(NSURL *)url +{ + if (m_URL != url) + { + [m_URL release]; + m_URL = [url copy]; + + //Create an asset for inspection of a resource referenced by a given URL. + //Load the values for the asset keys "tracks", "playable". + + AVURLAsset *asset = [AVURLAsset URLAssetWithURL:m_URL options:nil]; + NSArray *requestedKeys = [NSArray arrayWithObjects:AVF_TRACKS_KEY, AVF_PLAYABLE_KEY, nil]; + + // Tells the asset to load the values of any of the specified keys that are not already loaded. + [asset loadValuesAsynchronouslyForKeys:requestedKeys completionHandler: + ^{ + dispatch_async( dispatch_get_main_queue(), + ^{ + [self prepareToPlayAsset:asset withKeys:requestedKeys]; + }); + }]; + } +} + +- (void) unloadMedia +{ + if (m_player) + [m_player setRate:0.0]; + if (m_playerItem) { + [m_playerItem removeObserver:self forKeyPath:AVF_STATUS_KEY]; + + [[NSNotificationCenter defaultCenter] removeObserver:self + name:AVPlayerItemDidPlayToEndTimeNotification + object:m_playerItem]; + [[NSNotificationCenter defaultCenter] removeObserver:self + name:AVPlayerItemTimeJumpedNotification + object:m_playerItem]; + m_playerItem = 0; + } +} + +- (void) prepareToPlayAsset:(AVURLAsset *)asset + withKeys:(NSArray *)requestedKeys +{ + //Make sure that the value of each key has loaded successfully. + for (NSString *thisKey in requestedKeys) + { + NSError *error = nil; + AVKeyValueStatus keyStatus = [asset statusOfValueForKey:thisKey error:&error]; +#ifdef QT_DEBUG_AVF + qDebug() << Q_FUNC_INFO << [thisKey UTF8String] << " status: " << keyStatus; +#endif + if (keyStatus == AVKeyValueStatusFailed) + { + [self assetFailedToPrepareForPlayback:error]; + return; + } + } + + //Use the AVAsset playable property to detect whether the asset can be played. +#ifdef QT_DEBUG_AVF + qDebug() << Q_FUNC_INFO << "isPlayable: " << [asset isPlayable]; +#endif + if (!asset.playable) + { + //Generate an error describing the failure. + NSString *localizedDescription = NSLocalizedString(@"Item cannot be played", @"Item cannot be played description"); + NSString *localizedFailureReason = NSLocalizedString(@"The assets tracks were loaded, but could not be made playable.", @"Item cannot be played failure reason"); + NSDictionary *errorDict = [NSDictionary dictionaryWithObjectsAndKeys: + localizedDescription, NSLocalizedDescriptionKey, + localizedFailureReason, NSLocalizedFailureReasonErrorKey, + nil]; + NSError *assetCannotBePlayedError = [NSError errorWithDomain:@"StitchedStreamPlayer" code:0 userInfo:errorDict]; + + [self assetFailedToPrepareForPlayback:assetCannotBePlayedError]; + + return; + } + + m_audioAvailable = false; + m_videoAvailable = false; + + //Check each track of asset for audio and video content + NSArray *tracks = [asset tracks]; + for (AVAssetTrack *track in tracks) { + if ([track hasMediaCharacteristic:AVMediaCharacteristicAudible]) + m_audioAvailable = true; + if ([track hasMediaCharacteristic:AVMediaCharacteristicVisual]) + m_videoAvailable = true; + } + + //At this point we're ready to set up for playback of the asset. + //Stop observing our prior AVPlayerItem, if we have one. + if (m_playerItem) + { + //Remove existing player item key value observers and notifications. + [self unloadMedia]; + } + + //Create a new instance of AVPlayerItem from the now successfully loaded AVAsset. + m_playerItem = [AVPlayerItem playerItemWithAsset:asset]; + + //Observe the player item "status" key to determine when it is ready to play. + [m_playerItem addObserver:self + forKeyPath:AVF_STATUS_KEY + options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew + context:AVFMediaPlayerSessionObserverStatusObservationContext]; + + //When the player item has played to its end time we'll toggle + //the movie controller Pause button to be the Play button + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(playerItemDidReachEnd:) + name:AVPlayerItemDidPlayToEndTimeNotification + object:m_playerItem]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(playerItemTimeJumped:) + name:AVPlayerItemTimeJumpedNotification + object:m_playerItem]; + + + //Clean up old player if we have one + if (m_player) { + [m_player setRate:0.0]; + [m_player removeObserver:self forKeyPath:AVF_CURRENT_ITEM_KEY]; + [m_player removeObserver:self forKeyPath:AVF_RATE_KEY]; + [m_player release]; + m_player = 0; + + if (m_playerLayer) { + [m_playerLayer release]; + m_playerLayer = 0; //Will have been released + } + } + + //Get a new AVPlayer initialized to play the specified player item. + m_player = [AVPlayer playerWithPlayerItem:m_playerItem]; + [m_player retain]; + +#if defined(Q_OS_OSX) + //Set the initial volume on new player object + if (self.session) + m_player.volume = m_session->volume() / 100.0f; +#endif + + //Create a new player layer if we don't have one already + if (!m_playerLayer) + { + m_playerLayer = [AVPlayerLayer playerLayerWithPlayer:m_player]; + [m_playerLayer retain]; + m_playerLayer.videoGravity = AVLayerVideoGravityResizeAspectFill; + + //Get the native size of the new item, and reset the bounds of the player layer + AVAsset *asset = m_playerItem.asset; + if (asset) { + NSArray *tracks = [asset tracksWithMediaType:AVMediaTypeVideo]; + if ([tracks count]) { + AVAssetTrack *videoTrack = [tracks objectAtIndex:0]; + m_playerLayer.anchorPoint = CGPointMake(0.0f, 0.0f); + m_playerLayer.bounds = CGRectMake(0.0f, 0.0f, videoTrack.naturalSize.width, videoTrack.naturalSize.height); + } + } + + } + + //Observe the AVPlayer "currentItem" property to find out when any + //AVPlayer replaceCurrentItemWithPlayerItem: replacement will/did + //occur. + [m_player addObserver:self + forKeyPath:AVF_CURRENT_ITEM_KEY + options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew + context:AVFMediaPlayerSessionObserverCurrentItemObservationContext]; + + //Observe the AVPlayer "rate" property to update the scrubber control. + [m_player addObserver:self + forKeyPath:AVF_RATE_KEY + options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew + context:AVFMediaPlayerSessionObserverRateObservationContext]; + +} + +-(void) assetFailedToPrepareForPlayback:(NSError *)error +{ + Q_UNUSED(error) + QMetaObject::invokeMethod(m_session, "processMediaLoadError", Qt::AutoConnection); +#ifdef QT_DEBUG_AVF + qDebug() << Q_FUNC_INFO; + qDebug() << [[error localizedDescription] UTF8String]; + qDebug() << [[error localizedFailureReason] UTF8String]; + qDebug() << [[error localizedRecoverySuggestion] UTF8String]; +#endif +} + +- (void) playerItemDidReachEnd:(NSNotification *)notification +{ + Q_UNUSED(notification) + if (self.session) + QMetaObject::invokeMethod(m_session, "processEOS", Qt::AutoConnection); +} + +- (void) playerItemTimeJumped:(NSNotification *)notification +{ + Q_UNUSED(notification) + if (self.session) + QMetaObject::invokeMethod(m_session, "processPositionChange", Qt::AutoConnection); +} + +- (void) observeValueForKeyPath:(NSString*) path + ofObject:(id)object + change:(NSDictionary*)change + context:(void*)context +{ + //AVPlayerItem "status" property value observer. + if (context == AVFMediaPlayerSessionObserverStatusObservationContext) + { + AVPlayerStatus status = (AVPlayerStatus)[[change objectForKey:NSKeyValueChangeNewKey] integerValue]; + switch (status) + { + //Indicates that the status of the player is not yet known because + //it has not tried to load new media resources for playback + case AVPlayerStatusUnknown: + { + //QMetaObject::invokeMethod(m_session, "processLoadStateChange", Qt::AutoConnection); + } + break; + + case AVPlayerStatusReadyToPlay: + { + //Once the AVPlayerItem becomes ready to play, i.e. + //[playerItem status] == AVPlayerItemStatusReadyToPlay, + //its duration can be fetched from the item. + if (self.session) + QMetaObject::invokeMethod(m_session, "processLoadStateChange", Qt::AutoConnection); + } + break; + + case AVPlayerStatusFailed: + { + AVPlayerItem *playerItem = (AVPlayerItem *)object; + [self assetFailedToPrepareForPlayback:playerItem.error]; + + if (self.session) + QMetaObject::invokeMethod(m_session, "processLoadStateChange", Qt::AutoConnection); + } + break; + } + } + //AVPlayer "rate" property value observer. + else if (context == AVFMediaPlayerSessionObserverRateObservationContext) + { + //QMetaObject::invokeMethod(m_session, "setPlaybackRate", Qt::AutoConnection, Q_ARG(qreal, [m_player rate])); + } + //AVPlayer "currentItem" property observer. + //Called when the AVPlayer replaceCurrentItemWithPlayerItem: + //replacement will/did occur. + else if (context == AVFMediaPlayerSessionObserverCurrentItemObservationContext) + { + AVPlayerItem *newPlayerItem = [change objectForKey:NSKeyValueChangeNewKey]; + if (m_playerItem != newPlayerItem) + { + m_playerItem = newPlayerItem; + + //Get the native size of the new item, and reset the bounds of the player layer + //AVAsset *asset = m_playerItem.asset; + AVAsset *asset = [m_playerItem asset]; + if (asset) { + NSArray *tracks = [asset tracksWithMediaType:AVMediaTypeVideo]; + if ([tracks count]) { + AVAssetTrack *videoTrack = [tracks objectAtIndex:0]; + m_playerLayer.anchorPoint = CGPointMake(0.0f, 0.0f); + m_playerLayer.bounds = CGRectMake(0.0f, 0.0f, videoTrack.naturalSize.width, videoTrack.naturalSize.height); + } + } + + } + if (self.session) + QMetaObject::invokeMethod(m_session, "processCurrentItemChanged", Qt::AutoConnection); + } + else + { + [super observeValueForKeyPath:path ofObject:object change:change context:context]; + } +} + +- (void) detatchSession +{ +#ifdef QT_DEBUG_AVF + qDebug() << Q_FUNC_INFO; +#endif + m_session = 0; +} + +- (void) dealloc +{ +#ifdef QT_DEBUG_AVF + qDebug() << Q_FUNC_INFO; +#endif + [self unloadMedia]; + + if (m_player) { + [m_player removeObserver:self forKeyPath:AVF_CURRENT_ITEM_KEY]; + [m_player removeObserver:self forKeyPath:AVF_RATE_KEY]; + [m_player release]; + m_player = 0; + } + + if (m_playerLayer) { + [m_playerLayer release]; + m_playerLayer = 0; + } + + if (m_URL) { + [m_URL release]; + } + + [super dealloc]; +} + +@end + +AVFMediaPlayerSession::AVFMediaPlayerSession(AVFMediaPlayerService *service, QObject *parent) + : QObject(parent) + , m_service(service) + , m_videoOutput(0) + , m_state(QMediaPlayer::StoppedState) + , m_mediaStatus(QMediaPlayer::NoMedia) + , m_mediaStream(0) + , m_muted(false) + , m_tryingAsync(false) + , m_volume(100) + , m_rate(1.0) + , m_duration(0) + , m_videoAvailable(false) + , m_audioAvailable(false) +{ + m_observer = [[AVFMediaPlayerSessionObserver alloc] initWithMediaPlayerSession:this]; +} + +AVFMediaPlayerSession::~AVFMediaPlayerSession() +{ +#ifdef QT_DEBUG_AVF + qDebug() << Q_FUNC_INFO; +#endif + //Detatch the session from the sessionObserver (which could still be alive trying to communicate with this session). + [(AVFMediaPlayerSessionObserver*)m_observer detatchSession]; + [(AVFMediaPlayerSessionObserver*)m_observer release]; +} + +void AVFMediaPlayerSession::setVideoOutput(AVFVideoOutput *output) +{ +#ifdef QT_DEBUG_AVF + qDebug() << Q_FUNC_INFO << output; +#endif + + if (m_videoOutput == output) + return; + + //Set the current output layer to null to stop rendering + if (m_videoOutput) { + m_videoOutput->setLayer(0); + } + + m_videoOutput = output; + + if (m_videoOutput && m_state != QMediaPlayer::StoppedState) + m_videoOutput->setLayer([(AVFMediaPlayerSessionObserver*)m_observer playerLayer]); +} + +void *AVFMediaPlayerSession::currentAssetHandle() +{ +#ifdef QT_DEBUG_AVF + qDebug() << Q_FUNC_INFO; +#endif + AVAsset *currentAsset = [[(AVFMediaPlayerSessionObserver*)m_observer playerItem] asset]; + return currentAsset; +} + +QMediaPlayer::State AVFMediaPlayerSession::state() const +{ + return m_state; +} + +QMediaPlayer::MediaStatus AVFMediaPlayerSession::mediaStatus() const +{ + return m_mediaStatus; +} + +QMediaContent AVFMediaPlayerSession::media() const +{ + return m_resources; +} + +const QIODevice *AVFMediaPlayerSession::mediaStream() const +{ + return m_mediaStream; +} + +void AVFMediaPlayerSession::setMedia(const QMediaContent &content, QIODevice *stream) +{ +#ifdef QT_DEBUG_AVF + qDebug() << Q_FUNC_INFO << content.canonicalUrl(); +#endif + + m_resources = content; + m_mediaStream = stream; + + QMediaPlayer::MediaStatus oldMediaStatus = m_mediaStatus; + + if (content.isNull() || content.canonicalUrl().isEmpty()) { + [(AVFMediaPlayerSessionObserver*)m_observer unloadMedia]; + m_mediaStatus = QMediaPlayer::NoMedia; + if (m_state != QMediaPlayer::StoppedState) + Q_EMIT stateChanged(m_state = QMediaPlayer::StoppedState); + + if (m_mediaStatus != oldMediaStatus) + Q_EMIT mediaStatusChanged(m_mediaStatus); + Q_EMIT positionChanged(position()); + return; + } else { + + m_mediaStatus = QMediaPlayer::LoadingMedia; + if (m_mediaStatus != oldMediaStatus) + Q_EMIT mediaStatusChanged(m_mediaStatus); + } + //Load AVURLAsset + //initialize asset using content's URL + NSString *urlString = [NSString stringWithUTF8String:content.canonicalUrl().toEncoded().constData()]; + NSURL *url = [NSURL URLWithString:urlString]; + [(AVFMediaPlayerSessionObserver*)m_observer setURL:url]; +} + +qint64 AVFMediaPlayerSession::position() const +{ + AVPlayerItem *playerItem = [(AVFMediaPlayerSessionObserver*)m_observer playerItem]; + + if (!playerItem) + return 0; + + CMTime time = [playerItem currentTime]; + return static_cast(float(time.value) / float(time.timescale) * 1000.0f); +} + +qint64 AVFMediaPlayerSession::duration() const +{ +#ifdef QT_DEBUG_AVF + qDebug() << Q_FUNC_INFO; +#endif + AVPlayerItem *playerItem = [(AVFMediaPlayerSessionObserver*)m_observer playerItem]; + + if (!playerItem) + return 0; + + CMTime time = [playerItem duration]; + return static_cast(float(time.value) / float(time.timescale) * 1000.0f); +} + +int AVFMediaPlayerSession::bufferStatus() const +{ + //BUG: bufferStatus may be relevant? +#ifdef QT_DEBUG_AVF + qDebug() << Q_FUNC_INFO; +#endif + return 100; +} + +int AVFMediaPlayerSession::volume() const +{ + return m_volume; +} + +bool AVFMediaPlayerSession::isMuted() const +{ + return m_muted; +} + +bool AVFMediaPlayerSession::isAudioAvailable() const +{ + return [(AVFMediaPlayerSessionObserver*)m_observer audioAvailable]; +} + +bool AVFMediaPlayerSession::isVideoAvailable() const +{ + return [(AVFMediaPlayerSessionObserver*)m_observer videoAvailable]; +} + +bool AVFMediaPlayerSession::isSeekable() const +{ + return true; +} + +QMediaTimeRange AVFMediaPlayerSession::availablePlaybackRanges() const +{ + AVPlayerItem *playerItem = [(AVFMediaPlayerSessionObserver*)m_observer playerItem]; + + if (playerItem) { + QMediaTimeRange timeRanges; + + NSArray *ranges = [playerItem loadedTimeRanges]; + for (NSValue *timeRange in ranges) { + CMTimeRange currentTimeRange = [timeRange CMTimeRangeValue]; + qint64 startTime = qint64(float(currentTimeRange.start.value) / currentTimeRange.start.timescale * 1000.0); + timeRanges.addInterval(startTime, startTime + qint64(float(currentTimeRange.duration.value) / currentTimeRange.duration.timescale * 1000.0)); + } + if (!timeRanges.isEmpty()) + return timeRanges; + } + return QMediaTimeRange(0, duration()); +} + +qreal AVFMediaPlayerSession::playbackRate() const +{ + return m_rate; +} + +void AVFMediaPlayerSession::setPlaybackRate(qreal rate) +{ +#ifdef QT_DEBUG_AVF + qDebug() << Q_FUNC_INFO << rate; +#endif + + if (qFuzzyCompare(m_rate, rate)) + return; + + m_rate = rate; + + AVPlayer *player = [(AVFMediaPlayerSessionObserver*)m_observer player]; + + if (player != 0 && m_state == QMediaPlayer::PlayingState) { + [player setRate:m_rate]; + } +} + +void AVFMediaPlayerSession::setPosition(qint64 pos) +{ +#ifdef QT_DEBUG_AVF + qDebug() << Q_FUNC_INFO << pos; +#endif + + if ( !isSeekable() || pos == position()) + return; + + AVPlayerItem *playerItem = [(AVFMediaPlayerSessionObserver*)m_observer playerItem]; + + if (!playerItem) + return; + + if (duration() > 0) + pos = qMin(pos, duration()); + + CMTime newTime = [playerItem currentTime]; + newTime.value = (pos / 1000.0f) * newTime.timescale; + [playerItem seekToTime:newTime]; + + //reset the EndOfMedia status position is changed after playback is finished + if (m_mediaStatus == QMediaPlayer::EndOfMedia) + processLoadStateChange(); +} + +void AVFMediaPlayerSession::play() +{ +#ifdef QT_DEBUG_AVF + qDebug() << Q_FUNC_INFO << "currently: " << m_state; +#endif + + if (m_state == QMediaPlayer::PlayingState) + return; + + m_state = QMediaPlayer::PlayingState; + + if (m_videoOutput) { + m_videoOutput->setLayer([(AVFMediaPlayerSessionObserver*)m_observer playerLayer]); + } + + //reset the EndOfMedia status if the same file is played again + if (m_mediaStatus == QMediaPlayer::EndOfMedia) { + setPosition(0); + processLoadStateChange(); + } + + if (m_mediaStatus == QMediaPlayer::LoadedMedia || m_mediaStatus == QMediaPlayer::BufferedMedia) + [[(AVFMediaPlayerSessionObserver*)m_observer player] play]; + + //processLoadStateChange(); + Q_EMIT stateChanged(m_state); +} + +void AVFMediaPlayerSession::pause() +{ +#ifdef QT_DEBUG_AVF + qDebug() << Q_FUNC_INFO << "currently: " << m_state; +#endif + + if (m_state == QMediaPlayer::PausedState) + return; + + m_state = QMediaPlayer::PausedState; + + if (m_videoOutput) { + m_videoOutput->setLayer([(AVFMediaPlayerSessionObserver*)m_observer playerLayer]); + } + + //reset the EndOfMedia status if the same file is played again + if (m_mediaStatus == QMediaPlayer::EndOfMedia) + processLoadStateChange(); + + [[(AVFMediaPlayerSessionObserver*)m_observer player] pause]; + + //processLoadStateChange(); + Q_EMIT stateChanged(m_state); +} + +void AVFMediaPlayerSession::stop() +{ +#ifdef QT_DEBUG_AVF + qDebug() << Q_FUNC_INFO << "currently: " << m_state; +#endif + + if (m_state == QMediaPlayer::StoppedState) + return; + + m_state = QMediaPlayer::StoppedState; + m_rate = 0.0f; + [[(AVFMediaPlayerSessionObserver*)m_observer player] setRate:m_rate]; + setPosition(0); + + if (m_videoOutput) { + m_videoOutput->setLayer(0); + } + + processLoadStateChange(); + Q_EMIT stateChanged(m_state); + Q_EMIT positionChanged(position()); +} + +void AVFMediaPlayerSession::setVolume(int volume) +{ +#ifdef QT_DEBUG_AVF + qDebug() << Q_FUNC_INFO << volume; +#endif + + if (m_volume == volume) + return; + + m_volume = volume; + +#if defined(Q_OS_OSX) + AVPlayer *player = [(AVFMediaPlayerSessionObserver*)m_observer player]; + if (player) { + [[(AVFMediaPlayerSessionObserver*)m_observer player] setVolume:m_volume / 100.0f]; + } +#endif + + Q_EMIT volumeChanged(m_volume); +} + +void AVFMediaPlayerSession::setMuted(bool muted) +{ +#ifdef QT_DEBUG_AVF + qDebug() << Q_FUNC_INFO << muted; +#endif + if (m_muted == muted) + return; + + m_muted = muted; +#if defined(Q_OS_OSX) + [[(AVFMediaPlayerSessionObserver*)m_observer player] setMuted:m_muted]; +#endif + Q_EMIT mutedChanged(muted); +} + +void AVFMediaPlayerSession::processEOS() +{ + //AVPlayerItem has reached end of track/stream +#ifdef QT_DEBUG_AVF + qDebug() << Q_FUNC_INFO; +#endif + Q_EMIT positionChanged(position()); + m_mediaStatus = QMediaPlayer::EndOfMedia; + + Q_EMIT stateChanged(m_state = QMediaPlayer::StoppedState); + Q_EMIT mediaStatusChanged(m_mediaStatus); +} + +void AVFMediaPlayerSession::processLoadStateChange() +{ + AVPlayerStatus currentStatus = [[(AVFMediaPlayerSessionObserver*)m_observer player] status]; + +#ifdef QT_DEBUG_AVF + qDebug() << Q_FUNC_INFO << currentStatus; +#endif + + QMediaPlayer::MediaStatus newStatus = QMediaPlayer::NoMedia; + bool isPlaying = (m_state != QMediaPlayer::StoppedState); + + if (currentStatus == AVPlayerStatusReadyToPlay) { + qint64 currentDuration = duration(); + if (m_duration != currentDuration) + Q_EMIT durationChanged(m_duration = currentDuration); + + if (m_audioAvailable != isAudioAvailable()) + Q_EMIT audioAvailableChanged(m_audioAvailable = !m_audioAvailable); + + if (m_videoAvailable != isVideoAvailable()) + Q_EMIT videoAvailableChanged(m_videoAvailable = !m_videoAvailable); + + newStatus = isPlaying ? QMediaPlayer::BufferedMedia : QMediaPlayer::LoadedMedia; + + if (m_state == QMediaPlayer::PlayingState && [(AVFMediaPlayerSessionObserver*)m_observer player]) { + [[(AVFMediaPlayerSessionObserver*)m_observer player] setRate:m_rate]; + [[(AVFMediaPlayerSessionObserver*)m_observer player] play]; + } + } + + if (newStatus != m_mediaStatus) + Q_EMIT mediaStatusChanged(m_mediaStatus = newStatus); +} + +void AVFMediaPlayerSession::processPositionChange() +{ + Q_EMIT positionChanged(position()); +} + +void AVFMediaPlayerSession::processMediaLoadError() +{ + Q_EMIT error(QMediaPlayer::FormatError, tr("Failed to load media")); + Q_EMIT mediaStatusChanged(m_mediaStatus = QMediaPlayer::InvalidMedia); + Q_EMIT stateChanged(m_state = QMediaPlayer::StoppedState); +} + +void AVFMediaPlayerSession::processCurrentItemChanged() +{ +#ifdef QT_DEBUG_AVF + qDebug() << Q_FUNC_INFO; +#endif + + AVPlayerLayer *playerLayer = [(AVFMediaPlayerSessionObserver*)m_observer playerLayer]; + + if (m_videoOutput && m_state != QMediaPlayer::StoppedState) { + m_videoOutput->setLayer(playerLayer); + } + +}