diff --git a/include/cinder/app/cocoa/PlatformCocoa.h b/include/cinder/app/cocoa/PlatformCocoa.h index 1212234eb8..4efb448489 100644 --- a/include/cinder/app/cocoa/PlatformCocoa.h +++ b/include/cinder/app/cocoa/PlatformCocoa.h @@ -46,6 +46,8 @@ typedef uint32_t CGDisplayChangeSummaryFlags; typedef uint32_t CGDirectDisplayID; +struct GLFWmonitor; + namespace cinder { #if defined( CINDER_MAC ) class DisplayMac; @@ -151,6 +153,8 @@ class DisplayMac : public Display { public: NSScreen* getNsScreen() const; CGDirectDisplayID getCgDirectDisplayId() const { return mDirectDisplayId; } + //! Returns the GLFW monitor handle matching this display, or nullptr if none matches. Only meaningful when Cinder is built with the GLFW backend. + GLFWmonitor* getGlfwMonitor() const; std::string getName() const override; diff --git a/include/cinder/qtime/QuickTimeImplAvf.h b/include/cinder/qtime/QuickTimeImplAvf.h index 5fe2b3186e..3d80bd029c 100644 --- a/include/cinder/qtime/QuickTimeImplAvf.h +++ b/include/cinder/qtime/QuickTimeImplAvf.h @@ -32,6 +32,7 @@ #include "cinder/Thread.h" #include "cinder/Url.h" +#include #include typedef struct __CVBuffer *CVBufferRef; @@ -136,6 +137,8 @@ class MovieBase { * Returns a boolean value indicating whether the rate value can be played (some media types cannot be played backwards) */ bool setRate( float rate ); + //! Gets the playback rate. It might be different from the one that was set because setRate failed or palindrome loop kicked off. + float getRate() const; //! Sets the audio playback volume ranging from [0 - 1.0] void setVolume( float volume ); @@ -189,6 +192,7 @@ class MovieBase { int32_t mFrameCount; float mFrameRate; float mDuration; + float mPlayRate; std::atomic mAssetLoaded; bool mLoaded, mPlayThroughOk, mPlayable, mProtected; bool mPlayingForward, mLoop, mPalindrome; @@ -199,6 +203,7 @@ class MovieBase { AVPlayerItem* mPlayerItem; AVURLAsset* mAsset; AVPlayerItemVideoOutput* mPlayerVideoOutput; + dispatch_queue_t mOutputQueue; std::mutex mMutex; diff --git a/src/cinder/app/cocoa/PlatformCocoa.cpp b/src/cinder/app/cocoa/PlatformCocoa.cpp index 03ad4b3f08..c2c96b4397 100644 --- a/src/cinder/app/cocoa/PlatformCocoa.cpp +++ b/src/cinder/app/cocoa/PlatformCocoa.cpp @@ -44,6 +44,12 @@ #include #include +#if defined( CINDER_GLFW ) + #define GLFW_EXPOSE_NATIVE_COCOA + #include "glfw/glfw3.h" + #include "glfw/glfw3native.h" +#endif + using namespace std; namespace cinder { namespace app { @@ -439,6 +445,19 @@ NSScreen* DisplayMac::getNsScreen() const return findNsScreenForCgDirectDisplayId( mDirectDisplayId ); } +GLFWmonitor* DisplayMac::getGlfwMonitor() const +{ +#if defined( CINDER_GLFW ) + int count = 0; + GLFWmonitor** monitors = ::glfwGetMonitors( &count ); + for( int i = 0; i < count; ++i ) { + if( ::glfwGetCocoaMonitor( monitors[i] ) == mDirectDisplayId ) + return monitors[i]; + } +#endif + return nullptr; +} + DisplayRef app::PlatformCocoa::findFromCgDirectDisplayId( CGDirectDisplayID displayId ) { for( auto &display : getDisplays() ) { diff --git a/src/cinder/app/glfw/WindowImplGlfw.cpp b/src/cinder/app/glfw/WindowImplGlfw.cpp index 12cc32b331..bbf682f5f6 100644 --- a/src/cinder/app/glfw/WindowImplGlfw.cpp +++ b/src/cinder/app/glfw/WindowImplGlfw.cpp @@ -32,6 +32,7 @@ #if defined( CINDER_MAC ) #include "cinder/app/glfw/AppImplGlfwMac.h" + #include "cinder/app/cocoa/PlatformCocoa.h" #endif namespace cinder { namespace app { @@ -75,10 +76,12 @@ WindowImplGlfw::WindowImplGlfw( const Window::Format &format, WindowImplGlfw *sh auto windowSize = displayLinux->getSize(); mGlfwWindow = ::glfwCreateWindow( windowSize.x, windowSize.y, format.getTitle().c_str(), displayLinux->getGlfwMonitor(), sharedGlfwWindow ); #elif defined( CINDER_MAC ) - // On macOS, get the primary monitor for fullscreen - GLFWmonitor* primaryMonitor = ::glfwGetPrimaryMonitor(); + auto* displayMac = dynamic_cast( mDisplay.get() ); + GLFWmonitor* monitor = displayMac ? displayMac->getGlfwMonitor() : nullptr; + if( ! monitor ) + monitor = ::glfwGetPrimaryMonitor(); auto windowSize = mDisplay->getSize(); - mGlfwWindow = ::glfwCreateWindow( windowSize.x, windowSize.y, format.getTitle().c_str(), primaryMonitor, sharedGlfwWindow ); + mGlfwWindow = ::glfwCreateWindow( windowSize.x, windowSize.y, format.getTitle().c_str(), monitor, sharedGlfwWindow ); #endif mWindowedSize = format.getSize(); mWindowedPos = format.getPos(); @@ -140,8 +143,11 @@ void WindowImplGlfw::setFullScreen( bool fullScreen, const app::FullScreenOption cinder::app::DisplayLinux* displayLinux = dynamic_cast( mDisplay.get() ); ::glfwSetWindowMonitor( mGlfwWindow, displayLinux->getGlfwMonitor(), 0, 0, mDisplay->getWidth(), mDisplay->getHeight(), GLFW_DONT_CARE ); #elif defined( CINDER_MAC ) - GLFWmonitor* primaryMonitor = ::glfwGetPrimaryMonitor(); - ::glfwSetWindowMonitor( mGlfwWindow, primaryMonitor, 0, 0, mDisplay->getWidth(), mDisplay->getHeight(), GLFW_DONT_CARE ); + auto* displayMac = dynamic_cast( mDisplay.get() ); + GLFWmonitor* monitor = displayMac ? displayMac->getGlfwMonitor() : nullptr; + if( ! monitor ) + monitor = ::glfwGetPrimaryMonitor(); + ::glfwSetWindowMonitor( mGlfwWindow, monitor, 0, 0, mDisplay->getWidth(), mDisplay->getHeight(), GLFW_DONT_CARE ); #endif } else { diff --git a/src/cinder/qtime/QuickTimeImplAvf.mm b/src/cinder/qtime/QuickTimeImplAvf.mm index 476bc20a38..e100dbb981 100644 --- a/src/cinder/qtime/QuickTimeImplAvf.mm +++ b/src/cinder/qtime/QuickTimeImplAvf.mm @@ -183,6 +183,7 @@ - (void)outputSequenceWasFlushed:(AVPlayerItemOutput *)output mPlayerItem( nil ), mAsset( nil ), mPlayerVideoOutput( nil ), + mOutputQueue( nil ), mPlayerDelegate( nil ), mResponder( nullptr ), mAssetLoaded( false ) @@ -192,12 +193,28 @@ - (void)outputSequenceWasFlushed:(AVPlayerItemOutput *)output MovieBase::~MovieBase() { + // Stop the output delegate first and drain any in-flight background callback, + // e.g. outputSequenceWasFlushed. + if( mPlayerVideoOutput ) { + [mPlayerVideoOutput setDelegate:nil queue:nil]; + if( mOutputQueue ) { + dispatch_sync( mOutputQueue, ^{} ); + } + [mPlayerVideoOutput release]; + mPlayerVideoOutput = nil; + } + if( mOutputQueue ) { + dispatch_release( mOutputQueue ); + mOutputQueue = nil; + } + // remove all observers removeObservers(); // release resources for AVF objects. if( mPlayer ) { [mPlayer cancelPendingPrerolls]; + [mPlayer replaceCurrentItemWithPlayerItem:nil]; [mPlayer release]; } @@ -206,9 +223,13 @@ - (void)outputSequenceWasFlushed:(AVPlayerItemOutput *)output [mAsset release]; } - if( mPlayerVideoOutput ) { - [mPlayerVideoOutput release]; + if( mPlayerDelegate ) { + [mPlayerDelegate release]; + mPlayerDelegate = nil; } + + delete mResponder; + mResponder = nullptr; } float MovieBase::getPixelAspectRatio() const @@ -410,11 +431,20 @@ - (void)outputSequenceWasFlushed:(AVPlayerItemOutput *)output else success = [mPlayerItem canPlaySlowForward]; + mPlayRate = rate; [mPlayer setRate:rate]; return success; } +float MovieBase::getRate() const +{ + if( ! mPlayer || ! mPlayerItem ) + return 0.f; + + return mPlayer.rate; +} + void MovieBase::setVolume( float volume ) { if( ! mPlayer ) @@ -516,6 +546,7 @@ - (void)outputSequenceWasFlushed:(AVPlayerItemOutput *)output mHeight = -1; mDuration = -1; mFrameCount = -1; + mPlayRate = 1; } void MovieBase::initFromUrl( const Url& url ) @@ -700,8 +731,8 @@ - (void)outputSequenceWasFlushed:(AVPlayerItemOutput *)output void MovieBase::createPlayerItemOutput( const AVPlayerItem* playerItem ) { mPlayerVideoOutput = [[AVPlayerItemVideoOutput alloc] initWithPixelBufferAttributes:avPlayerItemOutputDictionary()]; - dispatch_queue_t outputQueue = dispatch_queue_create("movieVideoOutputQueue", DISPATCH_QUEUE_SERIAL); - [mPlayerVideoOutput setDelegate:mPlayerDelegate queue:outputQueue]; + mOutputQueue = dispatch_queue_create("movieVideoOutputQueue", DISPATCH_QUEUE_SERIAL); + [mPlayerVideoOutput setDelegate:mPlayerDelegate queue:mOutputQueue]; mPlayerVideoOutput.suppressesPlayerRendering = YES; [playerItem addOutput:mPlayerVideoOutput]; } @@ -758,6 +789,7 @@ - (void)outputSequenceWasFlushed:(AVPlayerItemOutput *)output { mSignalReady.emit(); + setRate(mPlayRate); // previous setRate calls fail while the player is not ready if( mPlaying ) play(); } @@ -765,9 +797,9 @@ - (void)outputSequenceWasFlushed:(AVPlayerItemOutput *)output void MovieBase::playerItemEnded() { if( mPalindrome ) { - float rate = -[mPlayer rate]; - mPlayingForward = (rate >= 0); - this->setRate( rate ); + mPlayRate = -mPlayRate; + mPlayingForward = (mPlayRate >= 0); + this->setRate( mPlayRate ); } else if( mLoop ) { this->seekToStart();