From 85285d986234d99da938e4b02a0a69fb8bbbbfdb Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 13 Nov 2014 21:26:17 +0300 Subject: [PATCH 1/2] fixed os x mouse input, window icon, qt os x build-from-source --- Telegram/SourceFiles/history.cpp | 2 +- Telegram/SourceFiles/mtproto/mtp.h | 5 ++- .../SourceFiles/mtproto/mtpFileLoader.cpp | 39 +++++++++++-------- Telegram/SourceFiles/mtproto/mtpFileLoader.h | 2 + Telegram/SourceFiles/overviewwidget.cpp | 29 +++++--------- Telegram/SourceFiles/pspecific_linux.cpp | 4 +- Telegram/SourceFiles/pspecific_linux.h | 1 + Telegram/SourceFiles/pspecific_mac.cpp | 4 +- Telegram/SourceFiles/pspecific_mac.h | 1 + Telegram/SourceFiles/pspecific_wnd.cpp | 2 +- Telegram/SourceFiles/pspecific_wnd.h | 1 + Telegram/SourceFiles/telegram.qrc | 1 + Telegram/SourceFiles/window.cpp | 5 +-- Telegram/SourceFiles/window.h | 2 - .../plugins/platforms/cocoa/qcocoawindow.mm | 13 ++++++- 15 files changed, 63 insertions(+), 48 deletions(-) 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]; From 4ee33d3bd92134e3480fbb8799e57d05e263e367 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 13 Nov 2014 21:26:39 +0300 Subject: [PATCH 2/2] added os x window icon and qt os x build fix to git --- Telegram/SourceFiles/art/iconbig128.png | Bin 0 -> 7007 bytes .../mediaplayer/avfmediaplayersession.mm | 851 ++++++++++++++++++ 2 files changed, 851 insertions(+) create mode 100644 Telegram/SourceFiles/art/iconbig128.png create mode 100644 Telegram/_qt_5_3_1_patch/qtmultimedia/src/plugins/avfoundation/mediaplayer/avfmediaplayersession.mm diff --git a/Telegram/SourceFiles/art/iconbig128.png b/Telegram/SourceFiles/art/iconbig128.png new file mode 100644 index 0000000000000000000000000000000000000000..32b44a5eaee7a8f74a4d85568c086c33a1e884fe GIT binary patch literal 7007 zcmaJ`XEw&x?WIMm>CS}9Qbe)rUU>G2*NEOD2SnfqLYu8 z81#>i7}^VQjRpXeRM7~ilLrjN=?HU$dnxPM=p$KtFF^NAV{q1OI z`2V|ldHu&5i86!zH{bu07-Q{@q0r zHy@M_(#;3Ksj2x_*Ej_Yp-yn`KZ`c)Xt|2R<1y+}lmi`+H_Hpv_f_bC<#ybBOEAx-oKZ5W=Tt^1O zeBu5uXKh~}FV4RTSA_p_E|UMq_iwE8Kj$L#k67{RWW@h$@BiBFzn88x^k@1X@m?GM zkw47)TJyfw;CUn8-7U)mg4~SE3j{>i{ThCj%L<52a7Ig(mGfp~MD8^?LkYkV4WgX5L5TX$iZJqiK*|NH=o$EHJ$x zWmgF2g!PE}9py9~xLXR(#fOZ}(uncdYPr=-oobQ3=JMp~(l|{`%{3BoP-N=OiJ9_| zP+D|Sk6nq|<;BfL&3X|CCgIiaEEqv_cMVIvb!6?@N4X|iW7CN^ZBV;&ov+nTqjp)L z{W9h%;~OMYH(O}JOa^6SYk?8f2R1TH-s4wFi5_TnU8lM5$3E8^=F|7;6 zAXg$YbGe^Bw^XFe`dvK<-od(e(T1Jb4G`YW9EzOX*ch&bjM+@ZA3z5@C=KpDb{_OQ zip3;=(R62hdIE5n+mN(N+yEoR31L)P-k=DUTee-W;mm_+qME+sI4|r_jyoWKOO>M5KE(VDik<;ljt8?JdOdZ=3i_W~%f3}^5Md{Pv(Zd?3Kd3b!%ECcXf z=~9PyZ~0D1#qM(BY}SG>zJ|p1>WL?oZpD30;~&;^qT1KuO4s55)fAJhS@zavg?)u0 zUH}i$NlkkfnEqz;xd0VJmaqoUydjCq&bhC3iT~?+=?+8)e|Ic@qGey0KV1JbY!j%K zhkqFObE4trr$;%r1JsKi)VPz8q*Hf+Q(m>GRB?CpER*2w&FJA7-+bG2WMX+{{|zZZ zClB!6S#nDWpLr3pi*@!>xK99~?Ckd7!*v5wEXM%o{z36=$=3V@?NfI?;IeCj>ru^Z zD(ZEHNqn*gC1N;X|E1REU66o_Ftc2>2b`-=he1zj6~21Grsc; z>p=0?@}ybgCq$RswQup+3YAp!bGP+fBvZ$*eDF3@3yTLs>IwsKgCMAxutA}sSLgjQ zi%(0&^AW@dY=StX2j8bMJhJ)RO@p%rr06>iC_$Qp=(EGR{2m{^uly1!G_-qCtRgX0>i$tusmG?Q1$_ zydG_BT*FM=bSs)a*3K+Msz58@OJv@Y)96-v1>@*pp%k*c=#jJ@TB2`$9=!MEX==Bj z%P9$x5%ons^^7yfsZ5?G4@_4eb=#`?Fh>c$A&tGUfnhK+y~%8hvaa(Hkvd=Iq%S?r z9h+A!6(R0Grk_~W$txHZply!Ga4OW{3Y(d-9@|o}mAl7QhN2i`d*l*d`5n_OR7J%* zVy30K^h*Qiu(-MY1Cy_u*RNzbH2ZcRDk~IvhOc^2_dI$!XW?mRVetU`0{8w>ChAz2 z0T0qvJ?<*`LWefe`ZCTl|4eUkb=zVvxZ;+*+%QJA^pJ&~n9!J``Weg4E7o-Ho8>`u zOT3Omse_|}-SyL^x@!saH*mh+%JY($@WhJA-E%7ST-SJ|a!0n{`^QKRp@G&L4VP~? z#R^I&$)|JheLFQj(tTmkf8Hsc-0_S-eI|Na=<*m8c=gZ`&#t_4im3Nx5H~@aKmYBb zqxWp%oJN)xN-I8Fx!o3n#0b&AiyC~;Ib>+ydHz;3!Uu@!5oE#u%S#%XL{e@MxFx{e zTiDYT9}PZsj~I7}Z*97*FmNK^_cn}+4mB%YGw28Y^5jE%!pb&JcOl;`!{UT46=q}X zhM&{rHs=DER3M&peQPI$H;w1lFp|i6Eq254o6UrAvJ9YgZya8G$e8tQp+Xn-5+wa? zp!Vyg{H@T{mYS8PtgiA%`Hi5Tq^M)?dj@?@{g39W2g`}~)q}h?Gwb127sTA8@$|m3 znUgD$hZoqp1j|jo=nZV%8Z9lc>6i5JI0a@9%Zu$h@mcO6^?T>rXZy#NP@Hs!pVpZp2$Y3Jd@Wne!k|y_82v_?A%|(h;OsVs2lBo`+d zd|Dswh=^C)8j^#s+uxqZ zwl$W`#~z8wKIIV1Z(`uKKgkiJhd+FUUOo3ei5s8n59xg@?H z#xLK#89WU*_7_-2-r1CGcBeYJc+RXwGe%`mU6rvJ-c96sVj6cP5_oRCvhAU}xAB2a zeo}?0-Z;%ZbZJh$ec5@-nDMYf&rXVWng_)HZbCT;wON!beluoGI_wWYrTld!L-q5(V+9}>lbZuL(QlF`p9C_hUS4U{T%qUp2&quwkFc1cBq+$^qsE05{%-;VLc&x#qSfDHkUKQ%w z)Dcq6h@M@=GBx2Bd*zR2z1;;9OQhe-G^4t*Yz6eJR(%n{ANQ{jMh57JVPfmy1BUwU z`eXCtm9psFvu?$pqZrB)TH11@_>ywnGxd6wgSJ<~`g}(rJ(NZYT_iHgzUv%~_Y9$n z!Eb0yp0F@9K60v*xjcJ0j~O$*S8)QBL;a$G-`-D*p``PRcWU03-V||%?+``yn~Me6o+Uj?Czn@5znYLGzo0X}G||+dp(tQ4_Fot=%}soT2Cd8R|<5J@LQM=Mg;}dsc5Pq6FaX|^f8l5s@eOY%4@%U8A`ah`J~*|9;kT_ za(B$eK1!8>J9OFZU@^kxB;LTvRQK*UGs)wX_aHk(rHH_tSI~JSjyi8jM?NM^^LNT_ zz;d(1%_GU%%WdJRoXm>p-`11PACGR05^Dw2Tlr;`cHWC?_7@mb0obR`0@tq_svG}a zvWX-yQ-0>#Spqc~#kU#wnR!q;gq_Z!pkgm5O0s#- z9PPLRD4e6~Q*Z{_=X3NXIRhCcO5Bz8U#EbD@V^d~`twAWU_Mv2`gh0MO0H4SM;w|4 zp!g|xUK!wxObxxr7{&iotqmp;H7?=`NG<yQ@rOC|2oEFDkc|yd0BXGcm>=CYnMB8$m>Fu<{+|HXeYrrqrn0HC6|+ zzqTKNr*m06ZdOq_+8EOZV|8!Pwgc8&aVH(%xd2PuGm*}Y6_8?TA6MDTJM2C7s4bD8FiKV9+9W3T$!|H^JOh$7xQumKO{-PVYM|mu<^WpV2;v zo2Qh~$rD|{hnUKQ!jdz%vBwCqH1rdV4lrDe1>^5tJXL?N<#8fqxgV0z=eZNdtp>l# zVo7?>^64=LYdjzq@v6YQBhVs;OU@CUKgXuKl^>KS)2ogBu#XNfxTle&(=MMj__pqb zMXTqlJtZ*ct^G#IicUit!b9EwhqBSZk5^Kp_$KTX+3L^UFU-M4O$^HMH>3FSd6AAL zPi@g?yr89NcKhxcOK+@IwVCz{yu$Kw{mTeo#Ov&#<;AX;Y~i1w0^DfaZ!2nwITBmC zg~u?;mEnP_{?U9vjoJ?c{DD3a?#UHo0gyXHcXvDT=Vl7%ZE9wy;c7JsrKOYIm#LFU z?8T|#J_>POv3`$AEJjFZ`=tW>hSJiU-$yn-?UH_i*WlUMe5u>0-bxcoEZDmL>z66f zvw+`k4gx|gq_02r!LtuNmx93C$2e7?6ZMJo#t$K`ph)X_$5#!^{2@{;cy8$NvFK-l z*65^2eq4V`)Le5~y+QKJ`R#W>!k2|MyD{rM^8@UOdH(Fw1Fy6%h{t+jc>j3J$-rciCgqi|H}@s-bbs5@hg99ilF= zL#+%nY@Evz{^RBmraDo3_n{3f0leG0agMP^IZ=TkOB7#+bf<3xs^+Kg?o$!<=Nf#> zOhFG)UY7gz*Mu>lNN(&;jMwSeom$YRjGNjf$)6m?NAY-NOSpLDBCTC6>}DhmeQHuJ zyJj|@=`~mJr1c9t&_yRv=G_R5hm=M?q|ZiXdRP>Yxbj}Cu?RF=H2y&UP;+ij6VZ}E z#CxqV`%_20rJE)DBOTT;Yl%WBjeY?X;eJiX#*BgAk8;CLxE?OIaR@ zsFB6?4D{6!?^+Zy3P!hZ@j+$bUg?KlP zZ!WzGx+k%06-T=J&;$OS{z;-7LK_@`8{6Qqb?JnK%;gMV&Tlay(1To56n<;aL*u4$8wyaEd0utu6ljgu`N6%geMQuL-bgP-`MygkcnEetT->1IUl1+8n!*+HI8uEqGwvAb05{Ke2(w5=g=b^EJe&ib{cDW#OSNtK8{^c zB?V5bDz&HVwhiVz0{@^5(NOq}RyC&7<-Tarxc`d%Hf12s_vzS`Ig|`}y*=IUh`mg# zUyxHR)g`m)&!BR)t-`s-9}%{P!1(#`_IhD4{%VWXX}uCZ$C?Y(G`xn3-cPz}uW?h6 zJ&7RyiOJ7x?c{ee6R6PZ{ao^FiL3K+6n)G^J-os14uv74~j(XPb0`7v5DR#hJ zp5H&y776ASNIwu;N`^n9JI&4A>(F2SSgd2Vn`O`5dt%3?N4m9PU-}%&V-1uW|mWgK5p(c})r7yZQf>1gBz9R%rl^>OI68u>7a$D!bm{z4t z(s|xysHx;gJHzCaj2KX%vGdO3x_s_rgkR<|$m$_b$?{2&|M4nN9I>-c*tUlM8_b3* zcH-tJtj0$qy6;|u`2dUG-_`Nx~3OVaOZAT=-B2^-oP>uE#32o zR^BoBtStnnp{V=zRF>(KL&&L`fAON@#YCrmHpL` zn(ED$uW5;TH%MA;g@7isb|k+Jj5_96Sm!rl@gmAA=<0P2nO=SrynL;NKD98uomtb8 zvG)ZU>JA~@uXKGoFMHeferR6%w>u_L%Q0pjnS)hTdG$lvQUwa*3BMM(A~~SKo=mKY zIka_xWVSbA9V>?>e?-5GGG9~5*~nIITH>dx<-{7)iLx)-(5gA>6OZ z(p58&xb^d?*A837D_9!k5SBR_^DUQ5?bDX8KA#mUD{8p6bG|2JR)r4g;)sHl^KRKY zY?WFC1{I4}7#p<##eWB10cJ_DKbWL?d3*)~A!nqvdXf zS&ntOXF9F^0^pdjO)YQrm><;Bj)^yBZhgh4v}7So`P7m367f8MX$_5fPoI)m70v{n6FbmjOyR782(4g0dc`fH{Cpve9xsi0NN=@L0b3 z>!udl5b?Qy&G0H2LtwVmlxfw&keQRDuH(Ae9d(@OXt*|nd=>a5#}gppqt?F`wR7uN zZO?hdn^Qs@lF{|#r-3ZM{Acu%;^oftyREY}`ohswOC_CZKWZybU#f9!&jZY{7IylK zm~rA=cqd7?Mq8B1$@mO=h%g)U%myn;bQI_t6Le8@ODrl3KY`CGeT<$}&^v6wy)-5^ z5g)EN?h%zoy1kd8wv%qH?MzO!Kv^Q?qIt@{PYEg{@_o9y6O}+J{JiKbbTngjV5yf+ zb@5(t_B7em(@!Hl`rJg?MP;50KjUZ|rcRvIFZ9j%*l++$$iZdA|HkAv z0nV`JOS6ngG{?$ z;x}-{%+La+ajTNYgZ8b4@Lf>{z>`n!L4-I&=WuUX*Dsg4NTQeZ_gAPZxIR#yhV9*7 zZ4W8dWDer!xrc7$rL>apDI1J?JKz!(2WZLf*}uXE0@Uq&G}K7bgZ}*T($z8o*J(IB F`X6{u + +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); + } + +}