├── .gitignore ├── ofxMacScreenRecorderExample ├── addons.make ├── bin │ └── data │ │ └── image.png ├── ofxMacScreenRecorderExample.xcodeproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ ├── xcshareddata │ │ └── xcschemes │ │ │ ├── ofxMacScreenRecorderExample Debug.xcscheme │ │ │ └── ofxMacScreenRecorderExample Release.xcscheme │ └── project.pbxproj ├── Makefile ├── Project.xcconfig ├── openFrameworks-Info.plist ├── src │ └── ofApp.cpp └── config.make ├── .github └── FUNDING.yml ├── README.md ├── LICENSE └── src ├── ofxMacScreenRecorder.h └── ofxMacScreenRecorder.mm /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | bin/ 3 | xcuserdata/ -------------------------------------------------------------------------------- /ofxMacScreenRecorderExample/addons.make: -------------------------------------------------------------------------------- 1 | ofxMacScreenRecorder 2 | -------------------------------------------------------------------------------- /ofxMacScreenRecorderExample/bin/data/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/2bbb/ofxMacScreenRecorder/HEAD/ofxMacScreenRecorderExample/bin/data/image.png -------------------------------------------------------------------------------- /ofxMacScreenRecorderExample/ofxMacScreenRecorderExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ofxMacScreenRecorderExample/Makefile: -------------------------------------------------------------------------------- 1 | # Attempt to load a config.make file. 2 | # If none is found, project defaults in config.project.make will be used. 3 | ifneq ($(wildcard config.make),) 4 | include config.make 5 | endif 6 | 7 | # make sure the the OF_ROOT location is defined 8 | ifndef OF_ROOT 9 | OF_ROOT=$(realpath ../../..) 10 | endif 11 | 12 | # call the project makefile! 13 | include $(OF_ROOT)/libs/openFrameworksCompiled/project/makefileCommon/compile.project.mk 14 | -------------------------------------------------------------------------------- /ofxMacScreenRecorderExample/Project.xcconfig: -------------------------------------------------------------------------------- 1 | //THE PATH TO THE ROOT OF OUR OF PATH RELATIVE TO THIS PROJECT. 2 | //THIS NEEDS TO BE DEFINED BEFORE CoreOF.xcconfig IS INCLUDED 3 | OF_PATH = ../../.. 4 | 5 | //THIS HAS ALL THE HEADER AND LIBS FOR OF CORE 6 | #include "../../../libs/openFrameworksCompiled/project/osx/CoreOF.xcconfig" 7 | 8 | //ICONS - NEW IN 0072 9 | ICON_NAME_DEBUG = icon-debug.icns 10 | ICON_NAME_RELEASE = icon.icns 11 | ICON_FILE_PATH = $(OF_PATH)/libs/openFrameworksCompiled/project/osx/ 12 | 13 | //IF YOU WANT AN APP TO HAVE A CUSTOM ICON - PUT THEM IN YOUR DATA FOLDER AND CHANGE ICON_FILE_PATH to: 14 | //ICON_FILE_PATH = bin/data/ 15 | 16 | OTHER_LDFLAGS = $(OF_CORE_LIBS) $(OF_CORE_FRAMEWORKS) 17 | HEADER_SEARCH_PATHS = $(OF_CORE_HEADERS) 18 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [2bbb] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | # patreon: # Replace with a single Patreon username 5 | # open_collective: # Replace with a single Open Collective username 6 | # ko_fi: # Replace with a single Ko-fi username 7 | # tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | # community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | # liberapay: # Replace with a single Liberapay username 10 | # issuehunt: # Replace with a single IssueHunt username 11 | # otechie: # Replace with a single Otechie username 12 | # custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /ofxMacScreenRecorderExample/openFrameworks-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | cc.openFrameworks.ofapp 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | APPL 15 | CFBundleSignature 16 | ???? 17 | CFBundleVersion 18 | 1.0 19 | CFBundleIconFile 20 | ${ICON} 21 | 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ofxMacScreenRecorder 2 | 3 | ## How to use 4 | 5 | ```cpp 6 | ofxMacScreenRecorder recorder; 7 | ... 8 | void setup() { 9 | ofxMacScreenRecorderSetting setting; 10 | setting.recordingArea.set(0, 0, 1920, 1080); 11 | setting.frameRate = 60.0f; 12 | recorder.setup(setting); 13 | } 14 | 15 | void keyPressed() { 16 | if(recorder.isRecordingNow()) recorder.stop(); 17 | else recorder.start(ofToDataPath("test")); // not need extension. 18 | } 19 | ``` 20 | 21 | ## Update history 22 | 23 | ### 2017/07/01 ver 0.01 release 24 | 25 | ## License 26 | 27 | MIT License. 28 | 29 | ## Author 30 | 31 | - ISHII 2bit [bufferRenaiss co., ltd.] 32 | - ishii[at]buffer-renaiss.com 33 | 34 | ## At the last 35 | 36 | Please create new issue, if there is a problem. And please throw pull request, if you have a cool idea!! -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 ISHII 2bit 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /ofxMacScreenRecorderExample/src/ofApp.cpp: -------------------------------------------------------------------------------- 1 | #include "ofMain.h" 2 | #include "ofxMacScreenRecorder.h" 3 | 4 | class ofApp : public ofBaseApp { 5 | ofxMacScreenRecorder recorder; 6 | ofxMacScreenRecorderSetting recorderSetting; 7 | ofImage image; 8 | public: 9 | void setup() { 10 | recorderSetting.codecType = ofxMacScreenRecorder::CodecType::ProRes4444; 11 | if(!recorder.setup(recorderSetting)) ofExit(-1); 12 | 13 | image.load("image.png"); 14 | recorder.registerFinishWritingCallback([this](const std::string &path) { 15 | ofLogNotice() << "success recording. save to: " << path; 16 | }); 17 | recorder.registerStartWritingCallback([this] { 18 | ofLogNotice() << "start" << std::endl; 19 | }); 20 | 21 | ofBackground(0); 22 | } 23 | void update() {} 24 | void draw() { 25 | ofSetColor(255); 26 | if(recorder.getStatus() != ofxMacScreenRecorder::Status::Recording || ofGetFrameNum() % 2) { 27 | image.draw(0, 0); 28 | } 29 | ofSetColor(255, 0, 0); 30 | ofDrawLine(0, 0, ofGetFrameNum() % ofGetWidth(), ofGetHeight()); 31 | ofSetColor(0); 32 | ofDrawBitmapString(ofToString(ofGetFrameNum()), 20, 20); 33 | } 34 | 35 | void exit() { 36 | recorder.stop(); 37 | } 38 | 39 | void keyPressed(int key) { 40 | if(key == 'r') { 41 | ofLogNotice() << ofGetWindowPositionX() << ", " << ofGetWindowPositionY(); 42 | recorder.setSetting(recorderSetting); 43 | recorder.start(ofToDataPath("./test")); 44 | } 45 | if(key == 's') { 46 | recorder.stop(); 47 | } 48 | if(key == 'f') { 49 | ofToggleFullscreen(); 50 | } 51 | } 52 | void keyReleased(int key) {} 53 | void mouseMoved(int x, int y ) {} 54 | void mouseDragged(int x, int y, int button) {} 55 | void mousePressed(int x, int y, int button) {} 56 | void mouseReleased(int x, int y, int button) {} 57 | void mouseEntered(int x, int y) {} 58 | void mouseExited(int x, int y) {} 59 | void windowResized(int w, int h) {} 60 | void dragEvent(ofDragInfo dragInfo) {} 61 | void gotMessage(ofMessage msg) {} 62 | }; 63 | 64 | int main() { 65 | ofSetupOpenGL(1280, 720, OF_WINDOW); 66 | ofRunApp(new ofApp()); 67 | } 68 | -------------------------------------------------------------------------------- /ofxMacScreenRecorderExample/ofxMacScreenRecorderExample.xcodeproj/xcshareddata/xcschemes/ofxMacScreenRecorderExample Debug.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 51 | 52 | 58 | 59 | 60 | 61 | 62 | 63 | 69 | 70 | 76 | 77 | 78 | 79 | 81 | 82 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /ofxMacScreenRecorderExample/ofxMacScreenRecorderExample.xcodeproj/xcshareddata/xcschemes/ofxMacScreenRecorderExample Release.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 51 | 52 | 58 | 59 | 60 | 61 | 62 | 63 | 69 | 70 | 76 | 77 | 78 | 79 | 81 | 82 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /src/ofxMacScreenRecorder.h: -------------------------------------------------------------------------------- 1 | // 2 | // ofxMacScreenRecorder.h 3 | // 4 | // Created by ISHII 2bit on 2017/07/01. 5 | // 6 | // 7 | 8 | #ifndef ofxMacScreenRecorder_h 9 | #define ofxMacScreenRecorder_h 10 | 11 | #include 12 | 13 | #include 14 | #include 15 | 16 | #include "ofRectangle.h" 17 | #include "ofEvents.h" 18 | 19 | #ifdef MAC_OS_X_VERSION_10_13 20 | # if MAC_OS_X_VERSION_10_13 <= MAC_OS_X_VERSION_MIN_REQUIRED 21 | # define BBB_IS_MACOS_13 1 22 | # endif 23 | #endif 24 | 25 | class ofxMacScreenRecorder { 26 | public: 27 | enum class CodecType : std::uint8_t { 28 | H264, 29 | JPEG, 30 | ProRes422, 31 | ProRes4444, 32 | #ifdef BBB_IS_MACOS_13 33 | HEVC 34 | #endif 35 | }; 36 | enum class Status : std::uint8_t { 37 | NotRecording, 38 | Preparing, 39 | Recording, 40 | Pause, 41 | }; 42 | ofEvent didOccurRuntimeError; 43 | ofEvent didFailedWriting; 44 | ofEvent didStartWriting; 45 | ofEvent didPauseWriting; 46 | ofEvent didResumeWriting; 47 | ofEvent willFinishWriting; 48 | ofEvent didFinishWriting; 49 | 50 | std::function runtimeErrorCallback{[](const std::string &){}}; 51 | std::function finishWritingCallback{[](const std::string &){}};; 52 | std::function failureWritingCallback{[](const std::string &){}};; 53 | std::function didStartCallback{[]{}}; 54 | std::function didPauseCallback{[]{}}; 55 | std::function didResumeCallback{[]{}}; 56 | 57 | ~ofxMacScreenRecorder() { 58 | ofRemoveListener(ofEvents().draw, this, &ofxMacScreenRecorder::setContext, OF_EVENT_ORDER_BEFORE_APP); 59 | ofRemoveListener(didStartWriting, this, &ofxMacScreenRecorder::startWriting); 60 | ofRemoveListener(didOccurRuntimeError, this, &ofxMacScreenRecorder::runtimeError); 61 | ofRemoveListener(didFailedWriting, this, &ofxMacScreenRecorder::failureWriting); 62 | ofRemoveListener(didFinishWriting, this, &ofxMacScreenRecorder::finishWriting); 63 | } 64 | struct Setting { 65 | Setting() {}; 66 | ofRectangle recordingArea{0, 0, -1, -1}; 67 | bool willRecordCursor{false}; 68 | bool willRecordAppWindow{true}; 69 | float frameRate{60.0f}; 70 | float scale{0.0f}; 71 | CodecType codecType{CodecType::H264}; 72 | }; 73 | 74 | bool setup(const Setting &setting = Setting()); 75 | bool setSetting(const Setting &setting = Setting()); 76 | bool start(const std::string &moviePath, bool overwrite = true); 77 | void stop(); 78 | bool isRecordingNow() const; 79 | Status getStatus() const; 80 | 81 | inline Setting &getSetting() { return setting; } 82 | 83 | inline void registerRuntimeErrorCallback(std::function callback) 84 | { runtimeErrorCallback = callback; }; 85 | inline void registerStartWritingCallback(std::function callback) 86 | { didStartCallback = callback; }; 87 | inline void registerFinishWritingCallback(std::function callback) 88 | { finishWritingCallback = callback; }; 89 | inline void registerFailureWritingCallback(std::function callback) 90 | { failureWritingCallback = callback; }; 91 | 92 | void setContext(); 93 | 94 | Setting setting; 95 | private: 96 | std::string moviePath; 97 | void *recorder; 98 | 99 | inline void setContext(ofEventArgs &) { setContext(); } 100 | inline void startWriting() 101 | { didStartCallback(); }; 102 | inline void runtimeError(std::string &errorString) 103 | { runtimeErrorCallback(errorString); }; 104 | inline void failureWriting(std::string &errorString) 105 | { failureWritingCallback(errorString); }; 106 | inline void finishWriting(std::string &path) 107 | { finishWritingCallback(path); }; 108 | }; 109 | 110 | using ofxMacScreenRecorderSetting = ofxMacScreenRecorder::Setting; 111 | 112 | #endif /* ofxMacScreenRecorder_h */ 113 | -------------------------------------------------------------------------------- /ofxMacScreenRecorderExample/config.make: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # CONFIGURE PROJECT MAKEFILE (optional) 3 | # This file is where we make project specific configurations. 4 | ################################################################################ 5 | 6 | ################################################################################ 7 | # OF ROOT 8 | # The location of your root openFrameworks installation 9 | # (default) OF_ROOT = ../../.. 10 | ################################################################################ 11 | # OF_ROOT = ../../.. 12 | 13 | ################################################################################ 14 | # PROJECT ROOT 15 | # The location of the project - a starting place for searching for files 16 | # (default) PROJECT_ROOT = . (this directory) 17 | # 18 | ################################################################################ 19 | # PROJECT_ROOT = . 20 | 21 | ################################################################################ 22 | # PROJECT SPECIFIC CHECKS 23 | # This is a project defined section to create internal makefile flags to 24 | # conditionally enable or disable the addition of various features within 25 | # this makefile. For instance, if you want to make changes based on whether 26 | # GTK is installed, one might test that here and create a variable to check. 27 | ################################################################################ 28 | # None 29 | 30 | ################################################################################ 31 | # PROJECT EXTERNAL SOURCE PATHS 32 | # These are fully qualified paths that are not within the PROJECT_ROOT folder. 33 | # Like source folders in the PROJECT_ROOT, these paths are subject to 34 | # exlclusion via the PROJECT_EXLCUSIONS list. 35 | # 36 | # (default) PROJECT_EXTERNAL_SOURCE_PATHS = (blank) 37 | # 38 | # Note: Leave a leading space when adding list items with the += operator 39 | ################################################################################ 40 | # PROJECT_EXTERNAL_SOURCE_PATHS = 41 | 42 | ################################################################################ 43 | # PROJECT EXCLUSIONS 44 | # These makefiles assume that all folders in your current project directory 45 | # and any listed in the PROJECT_EXTERNAL_SOURCH_PATHS are are valid locations 46 | # to look for source code. The any folders or files that match any of the 47 | # items in the PROJECT_EXCLUSIONS list below will be ignored. 48 | # 49 | # Each item in the PROJECT_EXCLUSIONS list will be treated as a complete 50 | # string unless teh user adds a wildcard (%) operator to match subdirectories. 51 | # GNU make only allows one wildcard for matching. The second wildcard (%) is 52 | # treated literally. 53 | # 54 | # (default) PROJECT_EXCLUSIONS = (blank) 55 | # 56 | # Will automatically exclude the following: 57 | # 58 | # $(PROJECT_ROOT)/bin% 59 | # $(PROJECT_ROOT)/obj% 60 | # $(PROJECT_ROOT)/%.xcodeproj 61 | # 62 | # Note: Leave a leading space when adding list items with the += operator 63 | ################################################################################ 64 | # PROJECT_EXCLUSIONS = 65 | 66 | ################################################################################ 67 | # PROJECT LINKER FLAGS 68 | # These flags will be sent to the linker when compiling the executable. 69 | # 70 | # (default) PROJECT_LDFLAGS = -Wl,-rpath=./libs 71 | # 72 | # Note: Leave a leading space when adding list items with the += operator 73 | ################################################################################ 74 | 75 | # Currently, shared libraries that are needed are copied to the 76 | # $(PROJECT_ROOT)/bin/libs directory. The following LDFLAGS tell the linker to 77 | # add a runtime path to search for those shared libraries, since they aren't 78 | # incorporated directly into the final executable application binary. 79 | # TODO: should this be a default setting? 80 | # PROJECT_LDFLAGS=-Wl,-rpath=./libs 81 | 82 | ################################################################################ 83 | # PROJECT DEFINES 84 | # Create a space-delimited list of DEFINES. The list will be converted into 85 | # CFLAGS with the "-D" flag later in the makefile. 86 | # 87 | # (default) PROJECT_DEFINES = (blank) 88 | # 89 | # Note: Leave a leading space when adding list items with the += operator 90 | ################################################################################ 91 | # PROJECT_DEFINES = 92 | 93 | ################################################################################ 94 | # PROJECT CFLAGS 95 | # This is a list of fully qualified CFLAGS required when compiling for this 96 | # project. These CFLAGS will be used IN ADDITION TO the PLATFORM_CFLAGS 97 | # defined in your platform specific core configuration files. These flags are 98 | # presented to the compiler BEFORE the PROJECT_OPTIMIZATION_CFLAGS below. 99 | # 100 | # (default) PROJECT_CFLAGS = (blank) 101 | # 102 | # Note: Before adding PROJECT_CFLAGS, note that the PLATFORM_CFLAGS defined in 103 | # your platform specific configuration file will be applied by default and 104 | # further flags here may not be needed. 105 | # 106 | # Note: Leave a leading space when adding list items with the += operator 107 | ################################################################################ 108 | # PROJECT_CFLAGS = 109 | 110 | ################################################################################ 111 | # PROJECT OPTIMIZATION CFLAGS 112 | # These are lists of CFLAGS that are target-specific. While any flags could 113 | # be conditionally added, they are usually limited to optimization flags. 114 | # These flags are added BEFORE the PROJECT_CFLAGS. 115 | # 116 | # PROJECT_OPTIMIZATION_CFLAGS_RELEASE flags are only applied to RELEASE targets. 117 | # 118 | # (default) PROJECT_OPTIMIZATION_CFLAGS_RELEASE = (blank) 119 | # 120 | # PROJECT_OPTIMIZATION_CFLAGS_DEBUG flags are only applied to DEBUG targets. 121 | # 122 | # (default) PROJECT_OPTIMIZATION_CFLAGS_DEBUG = (blank) 123 | # 124 | # Note: Before adding PROJECT_OPTIMIZATION_CFLAGS, please note that the 125 | # PLATFORM_OPTIMIZATION_CFLAGS defined in your platform specific configuration 126 | # file will be applied by default and further optimization flags here may not 127 | # be needed. 128 | # 129 | # Note: Leave a leading space when adding list items with the += operator 130 | ################################################################################ 131 | # PROJECT_OPTIMIZATION_CFLAGS_RELEASE = 132 | # PROJECT_OPTIMIZATION_CFLAGS_DEBUG = 133 | 134 | ################################################################################ 135 | # PROJECT COMPILERS 136 | # Custom compilers can be set for CC and CXX 137 | # (default) PROJECT_CXX = (blank) 138 | # (default) PROJECT_CC = (blank) 139 | # Note: Leave a leading space when adding list items with the += operator 140 | ################################################################################ 141 | # PROJECT_CXX = 142 | # PROJECT_CC = 143 | -------------------------------------------------------------------------------- /ofxMacScreenRecorderExample/ofxMacScreenRecorderExample.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 95A26E3B1F07267D00726F7B /* ofxMacScreenRecorder.mm in Sources */ = {isa = PBXBuildFile; fileRef = 95A26E3A1F07267D00726F7B /* ofxMacScreenRecorder.mm */; }; 11 | E4328149138ABC9F0047C5CB /* openFrameworksDebug.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E4328148138ABC890047C5CB /* openFrameworksDebug.a */; }; 12 | E4B69E210A3A1BDC003C02F2 /* ofApp.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E4B69E1E0A3A1BDC003C02F2 /* ofApp.cpp */; }; 13 | /* End PBXBuildFile section */ 14 | 15 | /* Begin PBXContainerItemProxy section */ 16 | E4328147138ABC890047C5CB /* PBXContainerItemProxy */ = { 17 | isa = PBXContainerItemProxy; 18 | containerPortal = E4328143138ABC890047C5CB /* openFrameworksLib.xcodeproj */; 19 | proxyType = 2; 20 | remoteGlobalIDString = E4B27C1510CBEB8E00536013; 21 | remoteInfo = openFrameworks; 22 | }; 23 | E4EEB9AB138B136A00A80321 /* PBXContainerItemProxy */ = { 24 | isa = PBXContainerItemProxy; 25 | containerPortal = E4328143138ABC890047C5CB /* openFrameworksLib.xcodeproj */; 26 | proxyType = 1; 27 | remoteGlobalIDString = E4B27C1410CBEB8E00536013; 28 | remoteInfo = openFrameworks; 29 | }; 30 | /* End PBXContainerItemProxy section */ 31 | 32 | /* Begin PBXCopyFilesBuildPhase section */ 33 | E4C2427710CC5ABF004149E2 /* CopyFiles */ = { 34 | isa = PBXCopyFilesBuildPhase; 35 | buildActionMask = 2147483647; 36 | dstPath = ""; 37 | dstSubfolderSpec = 10; 38 | files = ( 39 | ); 40 | runOnlyForDeploymentPostprocessing = 0; 41 | }; 42 | /* End PBXCopyFilesBuildPhase section */ 43 | 44 | /* Begin PBXFileReference section */ 45 | 95A26E3A1F07267D00726F7B /* ofxMacScreenRecorder.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ofxMacScreenRecorder.mm; path = ../../../addons/ofxMacScreenRecorder/src/ofxMacScreenRecorder.mm; sourceTree = ""; }; 46 | 95A26E3C1F07269C00726F7B /* ofxMacScreenRecorder.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofxMacScreenRecorder.h; path = ../../../addons/ofxMacScreenRecorder/src/ofxMacScreenRecorder.h; sourceTree = ""; }; 47 | E4328143138ABC890047C5CB /* openFrameworksLib.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = openFrameworksLib.xcodeproj; path = ../../../libs/openFrameworksCompiled/project/osx/openFrameworksLib.xcodeproj; sourceTree = SOURCE_ROOT; }; 48 | E4B69B5B0A3A1756003C02F2 /* ofxMacScreenRecorderExampleDebug.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ofxMacScreenRecorderExampleDebug.app; sourceTree = BUILT_PRODUCTS_DIR; }; 49 | E4B69E1E0A3A1BDC003C02F2 /* ofApp.cpp */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.cpp; fileEncoding = 30; name = ofApp.cpp; path = src/ofApp.cpp; sourceTree = SOURCE_ROOT; }; 50 | E4B6FCAD0C3E899E008CF71C /* openFrameworks-Info.plist */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.plist.xml; path = "openFrameworks-Info.plist"; sourceTree = ""; }; 51 | E4EB691F138AFCF100A09F29 /* CoreOF.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = CoreOF.xcconfig; path = ../../../libs/openFrameworksCompiled/project/osx/CoreOF.xcconfig; sourceTree = SOURCE_ROOT; }; 52 | E4EB6923138AFD0F00A09F29 /* Project.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Project.xcconfig; sourceTree = ""; }; 53 | /* End PBXFileReference section */ 54 | 55 | /* Begin PBXFrameworksBuildPhase section */ 56 | E4B69B590A3A1756003C02F2 /* Frameworks */ = { 57 | isa = PBXFrameworksBuildPhase; 58 | buildActionMask = 2147483647; 59 | files = ( 60 | E4328149138ABC9F0047C5CB /* openFrameworksDebug.a in Frameworks */, 61 | ); 62 | runOnlyForDeploymentPostprocessing = 0; 63 | }; 64 | /* End PBXFrameworksBuildPhase section */ 65 | 66 | /* Begin PBXGroup section */ 67 | 0EFB969330412775480C768D /* src */ = { 68 | isa = PBXGroup; 69 | children = ( 70 | 95A26E3C1F07269C00726F7B /* ofxMacScreenRecorder.h */, 71 | 95A26E3A1F07267D00726F7B /* ofxMacScreenRecorder.mm */, 72 | ); 73 | name = src; 74 | sourceTree = ""; 75 | }; 76 | 6948EE371B920CB800B5AC1A /* local_addons */ = { 77 | isa = PBXGroup; 78 | children = ( 79 | ); 80 | name = local_addons; 81 | sourceTree = ""; 82 | }; 83 | BB4B014C10F69532006C3DED /* addons */ = { 84 | isa = PBXGroup; 85 | children = ( 86 | CF30E786139621A98FE7F492 /* ofxMacScreenRecorder */, 87 | ); 88 | name = addons; 89 | sourceTree = ""; 90 | }; 91 | CF30E786139621A98FE7F492 /* ofxMacScreenRecorder */ = { 92 | isa = PBXGroup; 93 | children = ( 94 | 0EFB969330412775480C768D /* src */, 95 | ); 96 | name = ofxMacScreenRecorder; 97 | sourceTree = ""; 98 | }; 99 | E4328144138ABC890047C5CB /* Products */ = { 100 | isa = PBXGroup; 101 | children = ( 102 | E4328148138ABC890047C5CB /* openFrameworksDebug.a */, 103 | ); 104 | name = Products; 105 | sourceTree = ""; 106 | }; 107 | E4B69B4A0A3A1720003C02F2 = { 108 | isa = PBXGroup; 109 | children = ( 110 | E4B6FCAD0C3E899E008CF71C /* openFrameworks-Info.plist */, 111 | E4EB6923138AFD0F00A09F29 /* Project.xcconfig */, 112 | E4B69E1C0A3A1BDC003C02F2 /* src */, 113 | E4EEC9E9138DF44700A80321 /* openFrameworks */, 114 | BB4B014C10F69532006C3DED /* addons */, 115 | 6948EE371B920CB800B5AC1A /* local_addons */, 116 | E4B69B5B0A3A1756003C02F2 /* ofxMacScreenRecorderExampleDebug.app */, 117 | ); 118 | sourceTree = ""; 119 | }; 120 | E4B69E1C0A3A1BDC003C02F2 /* src */ = { 121 | isa = PBXGroup; 122 | children = ( 123 | E4B69E1E0A3A1BDC003C02F2 /* ofApp.cpp */, 124 | ); 125 | path = src; 126 | sourceTree = SOURCE_ROOT; 127 | }; 128 | E4EEC9E9138DF44700A80321 /* openFrameworks */ = { 129 | isa = PBXGroup; 130 | children = ( 131 | E4EB691F138AFCF100A09F29 /* CoreOF.xcconfig */, 132 | E4328143138ABC890047C5CB /* openFrameworksLib.xcodeproj */, 133 | ); 134 | name = openFrameworks; 135 | sourceTree = ""; 136 | }; 137 | /* End PBXGroup section */ 138 | 139 | /* Begin PBXNativeTarget section */ 140 | E4B69B5A0A3A1756003C02F2 /* ofxMacScreenRecorderExample */ = { 141 | isa = PBXNativeTarget; 142 | buildConfigurationList = E4B69B5F0A3A1757003C02F2 /* Build configuration list for PBXNativeTarget "ofxMacScreenRecorderExample" */; 143 | buildPhases = ( 144 | E4B69B580A3A1756003C02F2 /* Sources */, 145 | E4B69B590A3A1756003C02F2 /* Frameworks */, 146 | E4B6FFFD0C3F9AB9008CF71C /* ShellScript */, 147 | E4C2427710CC5ABF004149E2 /* CopyFiles */, 148 | ); 149 | buildRules = ( 150 | ); 151 | dependencies = ( 152 | E4EEB9AC138B136A00A80321 /* PBXTargetDependency */, 153 | ); 154 | name = ofxMacScreenRecorderExample; 155 | productName = myOFApp; 156 | productReference = E4B69B5B0A3A1756003C02F2 /* ofxMacScreenRecorderExampleDebug.app */; 157 | productType = "com.apple.product-type.application"; 158 | }; 159 | /* End PBXNativeTarget section */ 160 | 161 | /* Begin PBXProject section */ 162 | E4B69B4C0A3A1720003C02F2 /* Project object */ = { 163 | isa = PBXProject; 164 | attributes = { 165 | LastUpgradeCheck = 0600; 166 | }; 167 | buildConfigurationList = E4B69B4D0A3A1720003C02F2 /* Build configuration list for PBXProject "ofxMacScreenRecorderExample" */; 168 | compatibilityVersion = "Xcode 3.2"; 169 | developmentRegion = English; 170 | hasScannedForEncodings = 0; 171 | knownRegions = ( 172 | English, 173 | Japanese, 174 | French, 175 | German, 176 | ); 177 | mainGroup = E4B69B4A0A3A1720003C02F2; 178 | productRefGroup = E4B69B4A0A3A1720003C02F2; 179 | projectDirPath = ""; 180 | projectReferences = ( 181 | { 182 | ProductGroup = E4328144138ABC890047C5CB /* Products */; 183 | ProjectRef = E4328143138ABC890047C5CB /* openFrameworksLib.xcodeproj */; 184 | }, 185 | ); 186 | projectRoot = ""; 187 | targets = ( 188 | E4B69B5A0A3A1756003C02F2 /* ofxMacScreenRecorderExample */, 189 | ); 190 | }; 191 | /* End PBXProject section */ 192 | 193 | /* Begin PBXReferenceProxy section */ 194 | E4328148138ABC890047C5CB /* openFrameworksDebug.a */ = { 195 | isa = PBXReferenceProxy; 196 | fileType = archive.ar; 197 | path = openFrameworksDebug.a; 198 | remoteRef = E4328147138ABC890047C5CB /* PBXContainerItemProxy */; 199 | sourceTree = BUILT_PRODUCTS_DIR; 200 | }; 201 | /* End PBXReferenceProxy section */ 202 | 203 | /* Begin PBXShellScriptBuildPhase section */ 204 | E4B6FFFD0C3F9AB9008CF71C /* ShellScript */ = { 205 | isa = PBXShellScriptBuildPhase; 206 | buildActionMask = 2147483647; 207 | files = ( 208 | ); 209 | inputPaths = ( 210 | ); 211 | outputPaths = ( 212 | ); 213 | runOnlyForDeploymentPostprocessing = 0; 214 | shellPath = /bin/sh; 215 | shellScript = "mkdir -p \"$TARGET_BUILD_DIR/$PRODUCT_NAME.app/Contents/Resources/\"\n# Copy default icon file into App/Resources\nrsync -aved \"$ICON_FILE\" \"$TARGET_BUILD_DIR/$PRODUCT_NAME.app/Contents/Resources/\"\n# Copy libfmod and change install directory for fmod to run\nrsync -aved ../../../libs/fmodex/lib/osx/libfmodex.dylib \"$TARGET_BUILD_DIR/$PRODUCT_NAME.app/Contents/Frameworks/\";\ninstall_name_tool -change @executable_path/libfmodex.dylib @executable_path/../Frameworks/libfmodex.dylib \"$TARGET_BUILD_DIR/$PRODUCT_NAME.app/Contents/MacOS/$PRODUCT_NAME\";\n# Copy GLUT framework (must remove for AppStore submissions)\nrsync -aved ../../../libs/glut/lib/osx/GLUT.framework \"$TARGET_BUILD_DIR/$PRODUCT_NAME.app/Contents/Frameworks/\"\n"; 216 | }; 217 | /* End PBXShellScriptBuildPhase section */ 218 | 219 | /* Begin PBXSourcesBuildPhase section */ 220 | E4B69B580A3A1756003C02F2 /* Sources */ = { 221 | isa = PBXSourcesBuildPhase; 222 | buildActionMask = 2147483647; 223 | files = ( 224 | E4B69E210A3A1BDC003C02F2 /* ofApp.cpp in Sources */, 225 | 95A26E3B1F07267D00726F7B /* ofxMacScreenRecorder.mm in Sources */, 226 | ); 227 | runOnlyForDeploymentPostprocessing = 0; 228 | }; 229 | /* End PBXSourcesBuildPhase section */ 230 | 231 | /* Begin PBXTargetDependency section */ 232 | E4EEB9AC138B136A00A80321 /* PBXTargetDependency */ = { 233 | isa = PBXTargetDependency; 234 | name = openFrameworks; 235 | targetProxy = E4EEB9AB138B136A00A80321 /* PBXContainerItemProxy */; 236 | }; 237 | /* End PBXTargetDependency section */ 238 | 239 | /* Begin XCBuildConfiguration section */ 240 | E4B69B4E0A3A1720003C02F2 /* Debug */ = { 241 | isa = XCBuildConfiguration; 242 | baseConfigurationReference = E4EB6923138AFD0F00A09F29 /* Project.xcconfig */; 243 | buildSettings = { 244 | CONFIGURATION_BUILD_DIR = "$(SRCROOT)/bin/"; 245 | COPY_PHASE_STRIP = NO; 246 | DEAD_CODE_STRIPPING = YES; 247 | GCC_AUTO_VECTORIZATION = YES; 248 | GCC_ENABLE_SSE3_EXTENSIONS = YES; 249 | GCC_ENABLE_SUPPLEMENTAL_SSE3_INSTRUCTIONS = YES; 250 | GCC_INLINES_ARE_PRIVATE_EXTERN = NO; 251 | GCC_OPTIMIZATION_LEVEL = 0; 252 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 253 | GCC_WARN_ABOUT_DEPRECATED_FUNCTIONS = YES; 254 | GCC_WARN_ABOUT_INVALID_OFFSETOF_MACRO = NO; 255 | GCC_WARN_ALLOW_INCOMPLETE_PROTOCOL = NO; 256 | GCC_WARN_UNINITIALIZED_AUTOS = NO; 257 | GCC_WARN_UNUSED_VALUE = NO; 258 | GCC_WARN_UNUSED_VARIABLE = NO; 259 | HEADER_SEARCH_PATHS = ( 260 | "$(OF_CORE_HEADERS)", 261 | ../../../addons/ofxMacScreenRecorder/src, 262 | ); 263 | MACOSX_DEPLOYMENT_TARGET = 10.8; 264 | ONLY_ACTIVE_ARCH = YES; 265 | OTHER_CPLUSPLUSFLAGS = ( 266 | "-D__MACOSX_CORE__", 267 | "-mtune=native", 268 | ); 269 | SDKROOT = macosx; 270 | }; 271 | name = Debug; 272 | }; 273 | E4B69B4F0A3A1720003C02F2 /* Release */ = { 274 | isa = XCBuildConfiguration; 275 | baseConfigurationReference = E4EB6923138AFD0F00A09F29 /* Project.xcconfig */; 276 | buildSettings = { 277 | CONFIGURATION_BUILD_DIR = "$(SRCROOT)/bin/"; 278 | COPY_PHASE_STRIP = YES; 279 | DEAD_CODE_STRIPPING = YES; 280 | GCC_AUTO_VECTORIZATION = YES; 281 | GCC_ENABLE_SSE3_EXTENSIONS = YES; 282 | GCC_ENABLE_SUPPLEMENTAL_SSE3_INSTRUCTIONS = YES; 283 | GCC_INLINES_ARE_PRIVATE_EXTERN = NO; 284 | GCC_OPTIMIZATION_LEVEL = 3; 285 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 286 | GCC_UNROLL_LOOPS = YES; 287 | GCC_WARN_ABOUT_DEPRECATED_FUNCTIONS = YES; 288 | GCC_WARN_ABOUT_INVALID_OFFSETOF_MACRO = NO; 289 | GCC_WARN_ALLOW_INCOMPLETE_PROTOCOL = NO; 290 | GCC_WARN_UNINITIALIZED_AUTOS = NO; 291 | GCC_WARN_UNUSED_VALUE = NO; 292 | GCC_WARN_UNUSED_VARIABLE = NO; 293 | HEADER_SEARCH_PATHS = ( 294 | "$(OF_CORE_HEADERS)", 295 | ../../../addons/ofxMacScreenRecorder/src, 296 | ); 297 | MACOSX_DEPLOYMENT_TARGET = 10.8; 298 | OTHER_CPLUSPLUSFLAGS = ( 299 | "-D__MACOSX_CORE__", 300 | "-mtune=native", 301 | ); 302 | SDKROOT = macosx; 303 | }; 304 | name = Release; 305 | }; 306 | E4B69B600A3A1757003C02F2 /* Debug */ = { 307 | isa = XCBuildConfiguration; 308 | baseConfigurationReference = E4EB6923138AFD0F00A09F29 /* Project.xcconfig */; 309 | buildSettings = { 310 | COMBINE_HIDPI_IMAGES = YES; 311 | COPY_PHASE_STRIP = NO; 312 | FRAMEWORK_SEARCH_PATHS = ( 313 | "$(inherited)", 314 | "$(FRAMEWORK_SEARCH_PATHS_QUOTED_FOR_TARGET_1)", 315 | ); 316 | FRAMEWORK_SEARCH_PATHS_QUOTED_FOR_TARGET_1 = "\"$(SRCROOT)/../../../libs/glut/lib/osx\""; 317 | GCC_DYNAMIC_NO_PIC = NO; 318 | GCC_GENERATE_DEBUGGING_SYMBOLS = YES; 319 | GCC_MODEL_TUNING = NONE; 320 | HEADER_SEARCH_PATHS = ( 321 | "$(OF_CORE_HEADERS)", 322 | ../../../addons/ofxMacScreenRecorder/src, 323 | ); 324 | ICON = "$(ICON_NAME_DEBUG)"; 325 | ICON_FILE = "$(ICON_FILE_PATH)$(ICON)"; 326 | INFOPLIST_FILE = "openFrameworks-Info.plist"; 327 | INSTALL_PATH = /Applications; 328 | LIBRARY_SEARCH_PATHS = "$(inherited)"; 329 | PRODUCT_NAME = "$(TARGET_NAME)Debug"; 330 | WRAPPER_EXTENSION = app; 331 | }; 332 | name = Debug; 333 | }; 334 | E4B69B610A3A1757003C02F2 /* Release */ = { 335 | isa = XCBuildConfiguration; 336 | baseConfigurationReference = E4EB6923138AFD0F00A09F29 /* Project.xcconfig */; 337 | buildSettings = { 338 | COMBINE_HIDPI_IMAGES = YES; 339 | COPY_PHASE_STRIP = YES; 340 | FRAMEWORK_SEARCH_PATHS = ( 341 | "$(inherited)", 342 | "$(FRAMEWORK_SEARCH_PATHS_QUOTED_FOR_TARGET_1)", 343 | ); 344 | FRAMEWORK_SEARCH_PATHS_QUOTED_FOR_TARGET_1 = "\"$(SRCROOT)/../../../libs/glut/lib/osx\""; 345 | GCC_GENERATE_DEBUGGING_SYMBOLS = YES; 346 | GCC_MODEL_TUNING = NONE; 347 | HEADER_SEARCH_PATHS = ( 348 | "$(OF_CORE_HEADERS)", 349 | ../../../addons/ofxMacScreenRecorder/src, 350 | ); 351 | ICON = "$(ICON_NAME_RELEASE)"; 352 | ICON_FILE = "$(ICON_FILE_PATH)$(ICON)"; 353 | INFOPLIST_FILE = "openFrameworks-Info.plist"; 354 | INSTALL_PATH = /Applications; 355 | LIBRARY_SEARCH_PATHS = "$(inherited)"; 356 | PRODUCT_NAME = "$(TARGET_NAME)"; 357 | WRAPPER_EXTENSION = app; 358 | baseConfigurationReference = E4EB6923138AFD0F00A09F29; 359 | }; 360 | name = Release; 361 | }; 362 | /* End XCBuildConfiguration section */ 363 | 364 | /* Begin XCConfigurationList section */ 365 | E4B69B4D0A3A1720003C02F2 /* Build configuration list for PBXProject "ofxMacScreenRecorderExample" */ = { 366 | isa = XCConfigurationList; 367 | buildConfigurations = ( 368 | E4B69B4E0A3A1720003C02F2 /* Debug */, 369 | E4B69B4F0A3A1720003C02F2 /* Release */, 370 | ); 371 | defaultConfigurationIsVisible = 0; 372 | defaultConfigurationName = Release; 373 | }; 374 | E4B69B5F0A3A1757003C02F2 /* Build configuration list for PBXNativeTarget "ofxMacScreenRecorderExample" */ = { 375 | isa = XCConfigurationList; 376 | buildConfigurations = ( 377 | E4B69B600A3A1757003C02F2 /* Debug */, 378 | E4B69B610A3A1757003C02F2 /* Release */, 379 | ); 380 | defaultConfigurationIsVisible = 0; 381 | defaultConfigurationName = Release; 382 | }; 383 | /* End XCConfigurationList section */ 384 | }; 385 | rootObject = E4B69B4C0A3A1720003C02F2 /* Project object */; 386 | } 387 | -------------------------------------------------------------------------------- /src/ofxMacScreenRecorder.mm: -------------------------------------------------------------------------------- 1 | // 2 | // ofxMacScreenRecorder.mm 3 | // 4 | // Created by ISHII 2bit on 2017/07/01. 5 | // 6 | // 7 | 8 | #include 9 | #include 10 | 11 | #import "ofxMacScreenRecorder.h" 12 | #include "ofAppRunner.h" 13 | #include "ofEventUtils.h" 14 | #include "ofLog.h" 15 | 16 | #import 17 | #import 18 | #import 19 | 20 | @interface MacScreenRecorder : NSObject< 21 | AVCaptureFileOutputDelegate, 22 | AVCaptureFileOutputRecordingDelegate 23 | > 24 | { 25 | AVCaptureSession *captureSession; 26 | AVCaptureScreenInput *captureScreenInput; 27 | 28 | CGDirectDisplayID display; 29 | AVCaptureMovieFileOutput *captureMovieFileOutput; 30 | ofxMacScreenRecorder *parent; 31 | BOOL isRecordingNow; 32 | ofxMacScreenRecorder::Status status; 33 | } 34 | 35 | - (instancetype)init; 36 | - (BOOL)createCaptureSession:(NSError **)err; 37 | - (BOOL)start:(NSString *)moviePath overwrite:(BOOL)overwrite; 38 | - (void)stop; 39 | - (BOOL)isRecordingNow; 40 | - (ofxMacScreenRecorder::Status)status; 41 | - (void)setParent:(ofxMacScreenRecorder *)parent; 42 | 43 | @end 44 | 45 | @implementation MacScreenRecorder 46 | 47 | - (instancetype)init { 48 | self = [super init]; 49 | if(!self) return nil; 50 | 51 | return self; 52 | } 53 | 54 | - (void)dealloc { 55 | [[NSNotificationCenter defaultCenter] removeObserver:self 56 | name:AVCaptureSessionRuntimeErrorNotification 57 | object:captureSession]; 58 | [captureSession stopRunning]; 59 | [super dealloc]; 60 | } 61 | 62 | - (BOOL)createCaptureSession:(NSError **)err { 63 | captureSession = [[AVCaptureSession alloc] init]; 64 | if([captureSession canSetSessionPreset:AVCaptureSessionPresetHigh]) { 65 | [captureSession setSessionPreset:AVCaptureSessionPresetHigh]; 66 | } 67 | 68 | display = CGMainDisplayID(); 69 | 70 | captureScreenInput = [[AVCaptureScreenInput alloc] initWithDisplayID:display]; 71 | if([captureSession canAddInput:captureScreenInput]) { 72 | [captureSession addInput:captureScreenInput]; 73 | } else { 74 | return NO; 75 | } 76 | 77 | captureMovieFileOutput = [[AVCaptureMovieFileOutput alloc] init]; 78 | [captureMovieFileOutput setDelegate:self]; 79 | 80 | if([captureSession canAddOutput:captureMovieFileOutput]) { 81 | [captureSession addOutput:captureMovieFileOutput]; 82 | } else { 83 | return NO; 84 | } 85 | 86 | [NSNotificationCenter.defaultCenter addObserver:self 87 | selector:@selector(captureSessionRuntimeErrorDidOccur:) 88 | name:AVCaptureSessionRuntimeErrorNotification 89 | object:captureSession]; 90 | return YES; 91 | } 92 | 93 | - (void)startCaptureSession { 94 | [captureSession startRunning]; 95 | } 96 | 97 | - (float)maximumScreenInputFramerate { 98 | Float64 minimumVideoFrameInterval = CMTimeGetSeconds(captureScreenInput.minFrameDuration); 99 | return minimumVideoFrameInterval > 0.0f ? 1.0f / minimumVideoFrameInterval : 0.0; 100 | } 101 | 102 | - (void)setMaximumScreenInputFramerate:(float)maximumFramerate { 103 | CMTime minimumFrameDuration = CMTimeMake(1, (int32_t)maximumFramerate); 104 | [captureScreenInput setMinFrameDuration:minimumFrameDuration]; 105 | } 106 | 107 | - (void)setCapturesCursor:(BOOL)capturesCursor { 108 | [captureScreenInput setCapturesCursor:capturesCursor]; 109 | } 110 | 111 | -(void)addDisplayInputToCaptureSession:(CGDirectDisplayID)newDisplay 112 | cropRect:(CGRect)cropRect 113 | frameRate:(float)frameRate 114 | scale:(float)scale 115 | { 116 | [captureSession beginConfiguration]; 117 | if(newDisplay != display) { 118 | [captureSession removeInput:captureScreenInput]; 119 | AVCaptureScreenInput *newScreenInput = [[AVCaptureScreenInput alloc] initWithDisplayID:newDisplay]; 120 | 121 | captureScreenInput = newScreenInput; 122 | if ([captureSession canAddInput:captureScreenInput]) { 123 | [captureSession addInput:captureScreenInput]; 124 | } 125 | display = newDisplay; 126 | } 127 | [captureScreenInput setCropRect:cropRect]; 128 | [captureScreenInput setScaleFactor:scale]; 129 | [self setMaximumScreenInputFramerate:frameRate]; 130 | [captureSession commitConfiguration]; 131 | } 132 | 133 | - (void)setCaptureSessionPreset:(int)preset { 134 | [captureSession beginConfiguration]; 135 | if(![captureSession.sessionPreset isEqualToString:AVCaptureSessionPresetHigh]) { 136 | if([captureSession canSetSessionPreset:AVCaptureSessionPresetHigh]) { 137 | [captureSession setSessionPreset:AVCaptureSessionPresetHigh]; 138 | } 139 | } 140 | if([captureSession canSetSessionPreset:AVCaptureSessionPresetPhoto]) { 141 | [captureSession setSessionPreset:AVCaptureSessionPresetPhoto]; 142 | } 143 | [captureSession commitConfiguration]; 144 | } 145 | 146 | - (BOOL)captureOutputShouldProvideSampleAccurateRecordingStart:(AVCaptureOutput *)captureOutput { 147 | // We don't require frame accurate start when we start a recording. If we answer YES, the capture output 148 | // applies outputSettings immediately when the session starts previewing, resulting in higher CPU usage 149 | // and shorter battery life. 150 | return NO; 151 | } 152 | 153 | - (BOOL)start:(NSString *)moviePath overwrite:(BOOL)overwrite { 154 | isRecordingNow = YES; 155 | status = ofxMacScreenRecorder::Status::Preparing; 156 | // NSLog(@"Minimum Frame Duration: %f, Crop Rect: %@, Scale Factor: %f, Capture Mouse Clicks: %@, Capture Mouse Cursor: %@, Remove Duplicate Frames: %@", 157 | // CMTimeGetSeconds([captureScreenInput minFrameDuration]), 158 | // NSStringFromRect(NSRectFromCGRect([captureScreenInput cropRect])), 159 | // [captureScreenInput scaleFactor], 160 | // [captureScreenInput capturesMouseClicks] ? @"Yes" : @"No", 161 | // [captureScreenInput capturesCursor] ? @"Yes" : @"No", 162 | // [captureScreenInput removesDuplicateFrames] ? @"Yes" : @"No"); 163 | 164 | /* Create a recording file */ 165 | NSMutableDictionary *setting = @{}.mutableCopy; 166 | switch(parent->setting.codecType) { 167 | case ofxMacScreenRecorder::CodecType::H264: 168 | [setting setObject:AVVideoCodecH264 169 | forKey:AVVideoCodecKey]; 170 | break; 171 | case ofxMacScreenRecorder::CodecType::JPEG: 172 | [setting setObject:AVVideoCodecJPEG 173 | forKey:AVVideoCodecKey]; 174 | break; 175 | case ofxMacScreenRecorder::CodecType::ProRes422: 176 | [setting setObject:AVVideoCodecAppleProRes422 177 | forKey:AVVideoCodecKey]; 178 | break; 179 | case ofxMacScreenRecorder::CodecType::ProRes4444: 180 | [setting setObject:AVVideoCodecAppleProRes4444 181 | forKey:AVVideoCodecKey]; 182 | break; 183 | #ifdef BBB_IS_MACOS_13 184 | case ofxMacScreenRecorder::CodecType::HEVC: 185 | [setting setObject:AVVideoCodecHEVC 186 | forKey:AVVideoCodecKey]; 187 | break; 188 | #endif 189 | } 190 | [captureMovieFileOutput setOutputSettings:setting 191 | forConnection:captureMovieFileOutput.connections.firstObject]; 192 | 193 | if(overwrite) { 194 | NSString *path = [moviePath stringByAppendingString:@".mov"]; 195 | std::ifstream ifs(path.UTF8String); 196 | if(ifs.is_open()) { 197 | ofLogNotice() << "remove file " << path.UTF8String; 198 | std::remove(path.UTF8String); 199 | } 200 | } 201 | 202 | 203 | char *screenRecordingFileName = strdup(moviePath.stringByStandardizingPath.fileSystemRepresentation); 204 | if(screenRecordingFileName) { 205 | NSString *filenameStr = [NSFileManager.defaultManager stringWithFileSystemRepresentation:screenRecordingFileName length:strlen(screenRecordingFileName)]; 206 | NSURL *url = [NSURL fileURLWithPath:[filenameStr stringByAppendingPathExtension:@"mov"]]; 207 | [captureMovieFileOutput startRecordingToOutputFileURL:url 208 | recordingDelegate:self]; 209 | remove(screenRecordingFileName); 210 | free(screenRecordingFileName); 211 | return true; 212 | } 213 | 214 | status = ofxMacScreenRecorder::Status::NotRecording; 215 | isRecordingNow = NO; 216 | return false; 217 | } 218 | 219 | - (void)stop { 220 | isRecordingNow = NO; 221 | status = ofxMacScreenRecorder::Status::NotRecording; 222 | [captureMovieFileOutput stopRecording]; 223 | } 224 | 225 | - (void) captureOutput:(AVCaptureFileOutput *)output 226 | didStartRecordingToOutputFileAtURL:(NSURL *)fileURL 227 | fromConnections:(NSArray *)connections 228 | { 229 | ofLogNotice() << "started"; 230 | status = ofxMacScreenRecorder::Status::Recording; 231 | ofNotifyEvent(parent->didStartWriting, parent); 232 | } 233 | 234 | - (void) captureOutput:(AVCaptureFileOutput *)output 235 | didPauseRecordingToOutputFileAtURL:(NSURL *)fileURL 236 | fromConnections:(NSArray *)connections 237 | { 238 | status = ofxMacScreenRecorder::Status::Pause; 239 | ofNotifyEvent(parent->didPauseWriting, parent); 240 | } 241 | 242 | - (void) captureOutput:(AVCaptureFileOutput *)output 243 | didResumeRecordingToOutputFileAtURL:(NSURL *)fileURL 244 | fromConnections:(NSArray *)connections 245 | { 246 | status = ofxMacScreenRecorder::Status::Recording; 247 | ofNotifyEvent(parent->didResumeWriting, parent); 248 | } 249 | 250 | - (void) captureOutput:(AVCaptureFileOutput *)output 251 | willFinishRecordingToOutputFileAtURL:(NSURL *)fileURL 252 | fromConnections:(NSArray *)connections 253 | error:(nullable NSError *)error 254 | { 255 | ofNotifyEvent(parent->willFinishWriting, parent); 256 | } 257 | 258 | - (void) captureOutput:(AVCaptureFileOutput *)captureOutput 259 | didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL 260 | fromConnections:(NSArray *)connections 261 | error:(NSError *)error 262 | { 263 | status = ofxMacScreenRecorder::Status::NotRecording; 264 | isRecordingNow = NO; 265 | if(error) { 266 | std::string error_str = error.description.UTF8String; 267 | ofLogError("ofxMacScreenRecorder::finishRecording") << error_str; 268 | ofNotifyEvent(parent->didFailedWriting, error_str, parent); 269 | return; 270 | } 271 | std::string url_str = outputFileURL.absoluteString.UTF8String; 272 | ofNotifyEvent(parent->didFinishWriting, url_str, parent); 273 | } 274 | 275 | - (void)captureSessionRuntimeErrorDidOccur:(NSNotification *)notification { 276 | NSError *error = notification.userInfo[AVCaptureSessionErrorKey]; 277 | std::string error_str = error.description.UTF8String; 278 | ofLogError("ofxMacScreenRecorder::runtimeErrorDidOccur") << error_str; 279 | ofNotifyEvent(parent->didOccurRuntimeError, error_str, parent); 280 | isRecordingNow = NO; 281 | } 282 | 283 | - (BOOL)isRecordingNow { 284 | return isRecordingNow; 285 | } 286 | 287 | - (ofxMacScreenRecorder::Status)status { 288 | return status; 289 | } 290 | 291 | - (void)setParent:(ofxMacScreenRecorder *)parent_ { 292 | parent = parent_; 293 | } 294 | 295 | @end 296 | 297 | bool ofxMacScreenRecorder::setup(const ofxMacScreenRecorderSetting &setting) { 298 | this->setting = setting; 299 | MacScreenRecorder* rec = [MacScreenRecorder new]; 300 | this->recorder = rec; 301 | NSError *err = nil; 302 | BOOL b = [rec createCaptureSession:&err]; 303 | if(b) { 304 | [rec startCaptureSession]; 305 | if(setSetting(setting)) { 306 | this->recorder = rec; 307 | } else { 308 | return false; 309 | } 310 | } else { 311 | ofLogError("ofxMacScreenRecorder::setup") << "error at createCaptureSession: " << err.description.UTF8String; 312 | return false; 313 | } 314 | 315 | ofAddListener(ofEvents().draw, this, &ofxMacScreenRecorder::setContext, OF_EVENT_ORDER_BEFORE_APP); 316 | ofAddListener(didStartWriting, this, &ofxMacScreenRecorder::startWriting); 317 | ofAddListener(didOccurRuntimeError, this, &ofxMacScreenRecorder::runtimeError); 318 | ofAddListener(didFailedWriting, this, &ofxMacScreenRecorder::failureWriting); 319 | ofAddListener(didFinishWriting, this, &ofxMacScreenRecorder::finishWriting); 320 | setContext(); 321 | return true; 322 | } 323 | 324 | bool ofxMacScreenRecorder::setSetting(const ofxMacScreenRecorderSetting &setting) { 325 | this->setting = setting; 326 | MacScreenRecorder *recorder = (MacScreenRecorder *)this->recorder; 327 | [recorder setParent:this]; 328 | ofRectangle rect = setting.recordingArea; 329 | [recorder setCapturesCursor:setting.willRecordCursor]; 330 | if(setting.willRecordAppWindow) { 331 | rect = ofGetWindowRect(); 332 | rect.x = ofGetWindowPositionX(); 333 | rect.y = ofGetWindowPositionY(); 334 | } 335 | CGRect rect_ = CGRectMake(rect.x, rect.y, rect.width, rect.height); 336 | CGDirectDisplayID displays[16]; 337 | uint32_t matchingDisplayCount = 0; 338 | CGError err = CGGetDisplaysWithRect(rect_, 16, displays, &matchingDisplayCount); 339 | if(err != kCGErrorSuccess) { 340 | ofLogError("ofxMacScreenRecorder::setSetting") << "can't get displays with rect"; 341 | return false; 342 | } 343 | if(matchingDisplayCount != 1) { 344 | ofLogError("ofxMacScreenRecorder::setSetting") << "display area crossing multiple displays"; 345 | return false; 346 | } 347 | CGRect bound = CGDisplayBounds(displays[0]); 348 | CGDisplayModeRef modeRef = CGDisplayCopyDisplayMode(displays[0]); 349 | float w = CGDisplayModeGetPixelWidth(modeRef); 350 | float scale = bound.size.width / w; 351 | rect_.origin.y = bound.size.height - rect_.origin.y - rect_.size.height; 352 | CFRelease(modeRef); 353 | 354 | [recorder addDisplayInputToCaptureSession:displays[0] 355 | cropRect:rect_ 356 | frameRate:0.0 < setting.frameRate ? setting.frameRate : 60.0f 357 | scale:0.0 < setting.scale ? setting.scale : scale]; 358 | return true; 359 | } 360 | 361 | bool ofxMacScreenRecorder::start(const std::string &moviePath, bool overwrite) { 362 | MacScreenRecorder *recorder = (MacScreenRecorder *)this->recorder; 363 | if(recorder && ![recorder isRecordingNow]) { 364 | return [recorder start:[NSString stringWithUTF8String:moviePath.c_str()] 365 | overwrite:overwrite ? YES : NO]; 366 | } else { 367 | ofLogWarning("ofxMacScreenRecorder::start") << "already started recording"; 368 | return false; 369 | } 370 | } 371 | 372 | void ofxMacScreenRecorder::stop() { 373 | MacScreenRecorder *recorder = (MacScreenRecorder *)this->recorder; 374 | if(recorder && recorder.isRecordingNow) { 375 | [recorder stop]; 376 | } else { 377 | ofLogWarning("ofxMacScreenRecorder::stop") << "isn't recording now"; 378 | } 379 | } 380 | 381 | bool ofxMacScreenRecorder::isRecordingNow() const { 382 | if(this->recorder == NULL) return false; 383 | MacScreenRecorder *recorder = (MacScreenRecorder *)this->recorder; 384 | return recorder.isRecordingNow; 385 | } 386 | 387 | ofxMacScreenRecorder::Status ofxMacScreenRecorder::getStatus() const { 388 | if(this->recorder == NULL) return ofxMacScreenRecorder::Status::NotRecording; 389 | MacScreenRecorder *recorder = (MacScreenRecorder *)this->recorder; 390 | return recorder.status; 391 | } 392 | 393 | void ofxMacScreenRecorder::setContext() { 394 | NSOpenGLContext *context = (NSOpenGLContext *)ofGetNSGLContext(); 395 | [context makeCurrentContext]; 396 | } 397 | --------------------------------------------------------------------------------