├── .github ├── FUNDING.yml └── workflows │ ├── build.yml │ └── check_version.yml ├── .gitignore ├── Demo ├── Demo-iOS │ ├── Demo-iOS.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Demo-iOS.xcscheme │ └── Demo-iOS │ │ ├── Assets.xcassets │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── Contents.json │ │ ├── ContentView.swift │ │ ├── Demo_iOSApp.swift │ │ ├── Info.plist │ │ ├── Player │ │ ├── MPVPlayerDelegate.swift │ │ ├── MPVProperty.swift │ │ ├── Metal │ │ │ ├── MPVMetalPlayerView.swift │ │ │ ├── MPVMetalViewController.swift │ │ │ └── MetalLayer.swift │ │ └── OpenGL │ │ │ ├── MPVPlayerView.swift │ │ │ └── MPVViewController.swift │ │ └── Preview Content │ │ └── Preview Assets.xcassets │ │ └── Contents.json ├── Demo-macOS │ ├── Demo-macOS.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Demo-macOS.xcscheme │ └── Demo-macOS │ │ ├── Assets.xcassets │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── Contents.json │ │ ├── ContentView.swift │ │ ├── Demo_macOS.entitlements │ │ ├── Demo_macOSApp.swift │ │ ├── Info.plist │ │ ├── Player │ │ ├── MPVPlayerDelegate.swift │ │ ├── MPVProperty.swift │ │ ├── Metal │ │ │ ├── MPVMetalPlayerView.swift │ │ │ ├── MPVMetalViewController.swift │ │ │ └── MetalLayer.swift │ │ └── OpenGL │ │ │ ├── MPVOGLView.swift │ │ │ ├── MPVPlayerView.swift │ │ │ └── MPVViewController.swift │ │ └── Preview Content │ │ └── Preview Assets.xcassets │ │ └── Contents.json ├── Demo-tvOS │ ├── Demo-tvOS.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Demo-tvOS.xcscheme │ └── Demo-tvOS │ │ ├── Assets.xcassets │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ ├── App Icon & Top Shelf Image.brandassets │ │ │ ├── App Icon - App Store.imagestack │ │ │ │ ├── Back.imagestacklayer │ │ │ │ │ ├── Content.imageset │ │ │ │ │ │ └── Contents.json │ │ │ │ │ └── Contents.json │ │ │ │ ├── Contents.json │ │ │ │ ├── Front.imagestacklayer │ │ │ │ │ ├── Content.imageset │ │ │ │ │ │ └── Contents.json │ │ │ │ │ └── Contents.json │ │ │ │ └── Middle.imagestacklayer │ │ │ │ │ ├── Content.imageset │ │ │ │ │ └── Contents.json │ │ │ │ │ └── Contents.json │ │ │ ├── App Icon.imagestack │ │ │ │ ├── Back.imagestacklayer │ │ │ │ │ ├── Content.imageset │ │ │ │ │ │ └── Contents.json │ │ │ │ │ └── Contents.json │ │ │ │ ├── Contents.json │ │ │ │ ├── Front.imagestacklayer │ │ │ │ │ ├── Content.imageset │ │ │ │ │ │ └── Contents.json │ │ │ │ │ └── Contents.json │ │ │ │ └── Middle.imagestacklayer │ │ │ │ │ ├── Content.imageset │ │ │ │ │ └── Contents.json │ │ │ │ │ └── Contents.json │ │ │ ├── Contents.json │ │ │ ├── Top Shelf Image Wide.imageset │ │ │ │ └── Contents.json │ │ │ └── Top Shelf Image.imageset │ │ │ │ └── Contents.json │ │ └── Contents.json │ │ ├── ContentView.swift │ │ ├── Demo_tvOSApp.swift │ │ ├── Player │ │ ├── MPVPlayerDelegate.swift │ │ ├── MPVProperty.swift │ │ ├── Metal │ │ │ ├── MPVMetalPlayerView.swift │ │ │ ├── MPVMetalViewController.swift │ │ │ └── MetalLayer.swift │ │ └── OpenGL │ │ │ ├── MPVPlayerView.swift │ │ │ └── MPVViewController.swift │ │ └── Preview Content │ │ └── Preview Assets.xcassets │ │ └── Contents.json └── Demo.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── LICENSE ├── Makefile ├── Package.swift ├── README.md ├── Sources ├── BuildScripts │ ├── Package.swift │ ├── XCFrameworkBuild │ │ ├── base.swift │ │ └── main.swift │ └── patch │ │ ├── FFmpeg │ │ ├── 0001-hls-seek-patch-1.patch │ │ └── 0002-hls-seek-patch-2.patch │ │ └── libmpv │ │ └── 0001-player-add-moltenvk-context.patch ├── _FFmpeg-GPL │ ├── dummy.c │ └── include │ │ └── dummy.h ├── _FFmpeg │ ├── dummy.c │ └── include │ │ └── dummy.h ├── _MPVKit-GPL │ ├── dummy.c │ └── include │ │ └── dummy.h └── _MPVKit │ ├── dummy.c │ └── include │ └── dummy.h ├── docs └── Package.template.swift └── mpv.sh /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | ko_fi: cxfksword 4 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build and Release 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | version: 7 | description: 'Release tag name (default mpv version)' 8 | default: '' 9 | 10 | jobs: 11 | build: 12 | permissions: 13 | contents: write 14 | runs-on: macos-14 15 | steps: 16 | - uses: actions/checkout@v4 17 | 18 | - name: Check version to release 19 | uses: jannekem/run-python-script-action@v1 20 | with: 21 | script: | 22 | import re 23 | 24 | def normalize_version(version_string): 25 | version_string = re.sub(r'[^.0-9]+|-.+', '', version_string) 26 | parts = re.split(r'\.', version_string) 27 | major = int(parts[0]) 28 | minor = int(parts[1]) if len(parts) > 1 else 0 29 | patch = int(parts[2]) if len(parts) > 2 else 0 30 | return f"{major}.{minor}.{patch}" 31 | 32 | file_path = './Sources/BuildScripts/XCFrameworkBuild/main.swift' 33 | with open(file_path, 'r', encoding='utf-8') as file: 34 | content = file.read() 35 | 36 | mpvVersion = re.search(r'(case .libmpv[^"]+?)"(.+?)"', content).group(2) 37 | ffmpegVersion = re.search(r'(case .FFmpeg[^"]+?)"(.+?)"', content).group(2) 38 | libplaceboVersion = re.search(r'(case .libplacebo[^"]+?)"(.+?)"', content).group(2) 39 | vulkanVersion = re.search(r'(case .vulkan[^"]+?)"(.+?)"', content).group(2) 40 | 41 | print(f'mpv version: {mpvVersion}') 42 | print(f'ffmpeg version: {ffmpegVersion}') 43 | releaseVersion = '${{ github.event.inputs.version }}' or normalize_version(mpvVersion) 44 | print(f'release version: {releaseVersion}') 45 | set_env('BUILD_VERSION', mpvVersion) 46 | set_env('RELEASE_VERSION', releaseVersion) 47 | 48 | with open('/tmp/RELEASE_NOTE.txt', 'w', encoding='utf-8') as file: 49 | file.write(f''' 50 | * mpv version: {mpvVersion} ([changelog](https://github.com/mpv-player/mpv/releases/tag/{mpvVersion})) 51 | * ffmpeg version: {ffmpegVersion} ([changelog](https://github.com/FFmpeg/FFmpeg/blob/{ffmpegVersion}/Changelog)) 52 | * placebo version: v{libplaceboVersion} 53 | * MoltenVK version: v{vulkanVersion} 54 | ''') 55 | 56 | 57 | - name: Install dependencies 58 | run: | 59 | brew install autoconf 60 | brew install automake 61 | brew install libtool 62 | python -m pip install meson==1.4.2 63 | brew install ninja 64 | brew install rename 65 | 66 | - name: Setup Xcode to support visionOS 67 | run: | 68 | sudo xcode-select -s /Applications/Xcode_15.4.app/Contents/Developer 69 | xcodebuild -showsdks 70 | 71 | - name: Build GPL version 72 | run: | 73 | make build enable-gpl version=${{ env.RELEASE_VERSION }} 74 | 75 | cd ./dist/release 76 | rename 's/-all\.zip/-GPL-all\.zip/' *-all.zip 77 | rename 's/\.xcframework\.zip/-GPL\.xcframework\.zip/' *.xcframework.zip 78 | rename 's/\.xcframework\.checksum\.txt/-GPL\.xcframework\.checksum\.txt/' *.xcframework.checksum.txt 79 | 80 | - name: Clean GPL build files 81 | run: | 82 | # remove libbluray, same as LGPL version 83 | rm -rf ./dist/release/libbluray*.zip 84 | rm -rf ./dist/release/Libbluray*.zip 85 | rm -rf ./dist/release/Libbluray*.txt 86 | 87 | rm -rf ./dist/FFmpeg* 88 | rm -rf ./dist/release/FFmpeg 89 | rm -rf ./dist/libmpv* 90 | rm -rf ./dist/release/libmpv 91 | rm -rf ./dist/release/xcframework 92 | 93 | 94 | - name: Build LGPL version 95 | run: | 96 | make build version=${{ env.RELEASE_VERSION }} 97 | 98 | 99 | - name: Update Package.swift 100 | run: | 101 | rm -rf ./Package.swift 102 | cp -f ./dist/release/Package.swift ./Package.swift 103 | 104 | 105 | LibmpvGPL_url="https://github.com/mpvkit/MPVKit/releases/download/${{ env.RELEASE_VERSION }}/Libmpv-GPL.xcframework.zip" 106 | LibmpvGPL_checksum=$(cat ./dist/release/Libmpv-GPL.xcframework.checksum.txt) 107 | sed -i '' "s#\\\(Libmpv-GPL_url)#${LibmpvGPL_url}#g" ./Package.swift 108 | sed -i '' "s#\\\(Libmpv-GPL_checksum)#${LibmpvGPL_checksum}#g" ./Package.swift 109 | 110 | LibavcodecGPL_url="https://github.com/mpvkit/MPVKit/releases/download/${{ env.RELEASE_VERSION }}/Libavcodec-GPL.xcframework.zip" 111 | LibavcodecGPL_checksum=$(cat ./dist/release/Libavcodec-GPL.xcframework.checksum.txt) 112 | sed -i '' "s#\\\(Libavcodec-GPL_url)#${LibavcodecGPL_url}#g" ./Package.swift 113 | sed -i '' "s#\\\(Libavcodec-GPL_checksum)#${LibavcodecGPL_checksum}#g" ./Package.swift 114 | 115 | LibavdeviceGPL_url="https://github.com/mpvkit/MPVKit/releases/download/${{ env.RELEASE_VERSION }}/Libavdevice-GPL.xcframework.zip" 116 | LibavdeviceGPL_checksum=$(cat ./dist/release/Libavdevice-GPL.xcframework.checksum.txt) 117 | sed -i '' "s#\\\(Libavdevice-GPL_url)#${LibavdeviceGPL_url}#g" ./Package.swift 118 | sed -i '' "s#\\\(Libavdevice-GPL_checksum)#${LibavdeviceGPL_checksum}#g" ./Package.swift 119 | 120 | LibavformatGPL_url="https://github.com/mpvkit/MPVKit/releases/download/${{ env.RELEASE_VERSION }}/Libavformat-GPL.xcframework.zip" 121 | LibavformatGPL_checksum=$(cat ./dist/release/Libavformat-GPL.xcframework.checksum.txt) 122 | sed -i '' "s#\\\(Libavformat-GPL_url)#${LibavformatGPL_url}#g" ./Package.swift 123 | sed -i '' "s#\\\(Libavformat-GPL_checksum)#${LibavformatGPL_checksum}#g" ./Package.swift 124 | 125 | LibavfilterGPL_url="https://github.com/mpvkit/MPVKit/releases/download/${{ env.RELEASE_VERSION }}/Libavfilter-GPL.xcframework.zip" 126 | LibavfilterGPL_checksum=$(cat ./dist/release/Libavfilter-GPL.xcframework.checksum.txt) 127 | sed -i '' "s#\\\(Libavfilter-GPL_url)#${LibavfilterGPL_url}#g" ./Package.swift 128 | sed -i '' "s#\\\(Libavfilter-GPL_checksum)#${LibavfilterGPL_checksum}#g" ./Package.swift 129 | 130 | LibavutilGPL_url="https://github.com/mpvkit/MPVKit/releases/download/${{ env.RELEASE_VERSION }}/Libavutil-GPL.xcframework.zip" 131 | LibavutilGPL_checksum=$(cat ./dist/release/Libavutil-GPL.xcframework.checksum.txt) 132 | sed -i '' "s#\\\(Libavutil-GPL_url)#${LibavutilGPL_url}#g" ./Package.swift 133 | sed -i '' "s#\\\(Libavutil-GPL_checksum)#${LibavutilGPL_checksum}#g" ./Package.swift 134 | 135 | LibswresampleGPL_url="https://github.com/mpvkit/MPVKit/releases/download/${{ env.RELEASE_VERSION }}/Libswresample-GPL.xcframework.zip" 136 | LibswresampleGPL_checksum=$(cat ./dist/release/Libswresample-GPL.xcframework.checksum.txt) 137 | sed -i '' "s#\\\(Libswresample-GPL_url)#${LibswresampleGPL_url}#g" ./Package.swift 138 | sed -i '' "s#\\\(Libswresample-GPL_checksum)#${LibswresampleGPL_checksum}#g" ./Package.swift 139 | 140 | LibswscaleGPL_url="https://github.com/mpvkit/MPVKit/releases/download/${{ env.RELEASE_VERSION }}/Libswscale-GPL.xcframework.zip" 141 | LibswscaleGPL_checksum=$(cat ./dist/release/Libswscale-GPL.xcframework.checksum.txt) 142 | sed -i '' "s#\\\(Libswscale-GPL_url)#${LibswscaleGPL_url}#g" ./Package.swift 143 | sed -i '' "s#\\\(Libswscale-GPL_checksum)#${LibswscaleGPL_checksum}#g" ./Package.swift 144 | 145 | 146 | - name: Push Package.swift 147 | uses: EndBug/add-and-commit@v9 148 | with: 149 | default_author: github_actions 150 | add: | 151 | - Package.swift 152 | message: "chore: bump version to ${{ env.RELEASE_VERSION }}" 153 | push: "origin HEAD:${{ github.ref_name }}" 154 | 155 | - name: Upload binary to GitHub Release 156 | uses: softprops/action-gh-release@v2 157 | with: 158 | name: ${{ contains(env.RELEASE_VERSION, '-') && env.RELEASE_VERSION || env.BUILD_VERSION }} 159 | tag_name: ${{ env.RELEASE_VERSION }} 160 | files: | 161 | ./dist/release/*.txt 162 | ./dist/release/*.zip 163 | prerelease: ${{ contains(env.RELEASE_VERSION, '-') }} 164 | body_path: /tmp/RELEASE_NOTE.txt 165 | generate_release_notes: true 166 | fail_on_unmatched_files: true 167 | 168 | -------------------------------------------------------------------------------- /.github/workflows/check_version.yml: -------------------------------------------------------------------------------- 1 | name: Check new version 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | - cron: '0 0 * * 1' 7 | 8 | jobs: 9 | check: 10 | permissions: 11 | contents: write 12 | pull-requests: write 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | 17 | - name: Checkout ffmpeg 18 | uses: actions/checkout@v4 19 | with: 20 | repository: "FFmpeg/FFmpeg" 21 | path: 'ffmpeg' 22 | fetch-depth: 0 23 | 24 | - name: Checkout mpv 25 | uses: actions/checkout@v4 26 | with: 27 | repository: "mpv-player/mpv" 28 | path: 'mpv' 29 | fetch-depth: 0 30 | 31 | - name: Get latest verion 32 | id: version 33 | run: | 34 | cd ./ffmpeg 35 | latest_tag=$(git tag | grep n | grep -v "-" | sort -r | head -n 1) 36 | echo "ffmpeg latest tag: $latest_tag" 37 | echo "FFMPEG_LATEST_VERSION=$latest_tag" >> $GITHUB_ENV 38 | cd .. 39 | 40 | cd ./mpv 41 | latest_tag=$(git describe --tags `git rev-list --tags --max-count=1`) 42 | echo "mpv latest tag: $latest_tag" 43 | echo "MPV_LATEST_VERSION=$latest_tag" >> $GITHUB_ENV 44 | cd .. 45 | 46 | rm -rf ./ffmpeg 47 | rm -rf ./mpv 48 | 49 | - name: update to new version 50 | uses: jannekem/run-python-script-action@v1 51 | with: 52 | script: | 53 | import re 54 | 55 | def parse_version(ver): 56 | if '-' in ver or ver == '': 57 | return 0 58 | version_string = re.sub(r'[^.0-9]+', r'', ver) 59 | parts = re.split(r'\.', version_string) 60 | major = int(parts[0]) 61 | minor = int(parts[1]) if len(parts) > 1 else 0 62 | patch = int(parts[2]) if len(parts) > 2 else 0 63 | return int(f"{major}{minor}{patch}") 64 | 65 | file_path = './Sources/BuildScripts/XCFrameworkBuild/main.swift' 66 | with open(file_path, 'r', encoding='utf-8') as file: 67 | content = file.read() 68 | 69 | mpvOldVersion = re.search(r'(case .libmpv[^"]+?)"(.+?)"', content).group(2) 70 | print("mpv old version:", mpvOldVersion) 71 | set_env('MPV_OLD_VERSION', mpvOldVersion) 72 | mpvNewVersion = '${{ env.MPV_LATEST_VERSION }}' 73 | if parse_version(mpvNewVersion) > parse_version(mpvOldVersion): 74 | content = re.sub(r'(case .libmpv[^"]+?)"(.+?)"', r'\1"${{ env.MPV_LATEST_VERSION }}"', content, count=1) 75 | set_env('FOUND_NEW_MPV_VERSION', '1') 76 | 77 | ffmpegOldVersion = re.search(r'(case .FFmpeg[^"]+?)"(.+?)"', content).group(2) 78 | print("ffmpeg old version:", ffmpegOldVersion) 79 | set_env('FFMPEG_OLD_VERSION', ffmpegOldVersion) 80 | ffmpegNewVersion = '${{ env.FFMPEG_LATEST_VERSION }}' 81 | if parse_version(ffmpegNewVersion) > parse_version(ffmpegOldVersion): 82 | content = re.sub(r'(case .FFmpeg[^"]+?)"(.+?)"', r'\1"${{ env.FFMPEG_LATEST_VERSION }}"', content, count=1) 83 | set_env('FOUND_NEW_FFMPEG_VERSION', '1') 84 | 85 | with open(file_path, 'w', encoding='utf-8') as file: 86 | file.write(content) 87 | 88 | 89 | file_path = './README.md' 90 | with open(file_path, 'r', encoding='utf-8') as file: 91 | content = file.read() 92 | if parse_version(mpvNewVersion) > parse_version(mpvOldVersion): 93 | content = re.sub(r'mpv-.*-blue', r'mpv-${{ env.MPV_LATEST_VERSION }}-blue', content, count=1) 94 | if parse_version(ffmpegNewVersion) > parse_version(ffmpegOldVersion): 95 | content = re.sub(r'ffmpeg-.*-blue', r'ffmpeg-${{ env.FFMPEG_LATEST_VERSION }}-blue', content, count=1) 96 | with open(file_path, 'w', encoding='utf-8') as file: 97 | file.write(content) 98 | 99 | 100 | - name: Create Pull Request 101 | if: env.FOUND_NEW_MPV_VERSION && !env.FOUND_NEW_FFMPEG_VERSION 102 | uses: peter-evans/create-pull-request@v6 103 | with: 104 | add-paths: | 105 | ./Sources/BuildScripts/XCFrameworkBuild/main.swift 106 | ./README.md 107 | title: "mpv bump version to ${{ env.MPV_LATEST_VERSION }}" 108 | body: | 109 | https://github.com/mpv-player/mpv/releases/tag/${{ env.MPV_LATEST_VERSION }} 110 | https://github.com/mpv-player/mpv/compare/${{ env.MPV_OLD_VERSION }}...${{ env.MPV_LATEST_VERSION }} 111 | commit-message: "chore: mpv bump version to ${{ env.MPV_LATEST_VERSION }}" 112 | 113 | - name: Create Pull Request 114 | if: env.FOUND_NEW_FFMPEG_VERSION && !env.FOUND_NEW_MPV_VERSION 115 | uses: peter-evans/create-pull-request@v6 116 | with: 117 | add-paths: | 118 | ./Sources/BuildScripts/XCFrameworkBuild/main.swift 119 | ./README.md 120 | title: "FFmpeg bump version to ${{ env.FFMPEG_LATEST_VERSION }}" 121 | body: | 122 | https://github.com/FFmpeg/FFmpeg/blob/${{ env.FFMPEG_LATEST_VERSION }}/Changelog 123 | https://github.com/FFmpeg/FFmpeg/compare/${{ env.FFMPEG_OLD_VERSION }}...${{ env.FFMPEG_LATEST_VERSION }} 124 | commit-message: "chore: FFmpeg bump version to ${{ env.FFMPEG_LATEST_VERSION }}" 125 | 126 | - name: Create Pull Request 127 | if: env.FOUND_NEW_FFMPEG_VERSION && env.FOUND_NEW_MPV_VERSION 128 | uses: peter-evans/create-pull-request@v6 129 | with: 130 | add-paths: | 131 | ./Sources/BuildScripts/XCFrameworkBuild/main.swift 132 | ./README.md 133 | title: "mpv ${{ env.MPV_LATEST_VERSION }} && FFmpeg ${{ env.FFMPEG_LATEST_VERSION }}" 134 | body: | 135 | https://github.com/mpv-player/mpv/releases/tag/${{ env.MPV_LATEST_VERSION }} 136 | https://github.com/mpv-player/mpv/compare/${{ env.MPV_OLD_VERSION }}...${{ env.MPV_LATEST_VERSION }} 137 | 138 | https://github.com/FFmpeg/FFmpeg/blob/${{ env.FFMPEG_LATEST_VERSION }}/Changelog 139 | https://github.com/FFmpeg/FFmpeg/compare/${{ env.FFMPEG_OLD_VERSION }}...${{ env.FFMPEG_LATEST_VERSION }} 140 | commit-message: "chore: mpv ${{ env.MPV_LATEST_VERSION }} && FFmpeg ${{ env.FFMPEG_LATEST_VERSION }}" 141 | 142 | check-deps: 143 | permissions: 144 | contents: write 145 | pull-requests: write 146 | strategy: 147 | matrix: 148 | library: 149 | - name: libass 150 | repository: "mpvkit/libass-build" 151 | - name: vulkan 152 | repository: "mpvkit/moltenvk-build" 153 | - name: libplacebo 154 | repository: "mpvkit/libplacebo-build" 155 | - name: libshaderc 156 | repository: "mpvkit/libshaderc-build" 157 | - name: libdovi 158 | repository: "mpvkit/libdovi-build" 159 | runs-on: ubuntu-latest 160 | steps: 161 | - uses: actions/checkout@v4 162 | 163 | - name: Checkout latest code 164 | uses: actions/checkout@v4 165 | with: 166 | repository: ${{ matrix.library.repository }} 167 | path: 'latest_code' 168 | fetch-depth: 0 169 | 170 | - name: Get latest verion 171 | id: version 172 | run: | 173 | cd ./latest_code 174 | latest_tag=$(git tag --sort=-v:refname | grep -v "-" | head -n 1) 175 | echo "latest tag: $latest_tag" 176 | echo "LATEST_VERSION=$latest_tag" >> $GITHUB_ENV 177 | rm -rf ../latest_code 178 | 179 | - name: update to new version 180 | uses: jannekem/run-python-script-action@v1 181 | with: 182 | script: | 183 | import re 184 | 185 | def parse_version(ver): 186 | if '-' in ver or ver == '': 187 | return 0 188 | version_string = re.sub(r'[^.0-9]+', r'', ver) 189 | parts = re.split(r'\.', version_string) 190 | major = int(parts[0]) 191 | minor = int(parts[1]) if len(parts) > 1 else 0 192 | patch = int(parts[2]) if len(parts) > 2 else 0 193 | return int(f"{major}{minor}{patch}") 194 | 195 | file_path = './Sources/BuildScripts/XCFrameworkBuild/main.swift' 196 | with open(file_path, 'r', encoding='utf-8') as file: 197 | content = file.read() 198 | oldVersion = re.search(r'(case .${{ matrix.library.name }}[^"]+?)"(.+?)"', content).group(2) 199 | print("old version:", oldVersion) 200 | newVersion = '${{ env.LATEST_VERSION }}' 201 | print("new version:", newVersion) 202 | if parse_version(newVersion) > parse_version(oldVersion): 203 | content = re.sub(r'(case .${{ matrix.library.name }}[^"]+?)"(.+?)"', r'\1"${{ env.LATEST_VERSION }}"', content, count=1) 204 | set_env('FOUND_NEW_VERSION', '1') 205 | with open(file_path, 'w', encoding='utf-8') as file: 206 | file.write(content) 207 | 208 | 209 | - name: Create Pull Request 210 | if: env.FOUND_NEW_VERSION 211 | uses: peter-evans/create-pull-request@v6 212 | with: 213 | add-paths: | 214 | ./Sources/BuildScripts/XCFrameworkBuild/main.swift 215 | branch: "create-pull-request/${{ matrix.library.name }}" 216 | delete-branch: true 217 | title: "${{ matrix.library.name }} bump version to ${{ env.LATEST_VERSION }}" 218 | body: | 219 | https://github.com/${{ matrix.library.repository }}/releases/tag/${{ env.LATEST_VERSION }} 220 | commit-message: "chore: ${{ matrix.library.name }} bump version to ${{ env.LATEST_VERSION }}" 221 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io/api/osx,swift 2 | # Edit at https://www.gitignore.io/?templates=osx,swift 3 | 4 | ### Swift ### 5 | # Xcode 6 | # 7 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 8 | 9 | ## Build generated 10 | build/ 11 | DerivedData/ 12 | 13 | ## Various settings 14 | *.pbxuser 15 | !default.pbxuser 16 | *.mode1v3 17 | !default.mode1v3 18 | *.mode2v3 19 | !default.mode2v3 20 | *.perspectivev3 21 | !default.perspectivev3 22 | xcuserdata/ 23 | 24 | ## Other 25 | *.moved-aside 26 | *.xccheckout 27 | *.xcscmblueprint 28 | 29 | ## Obj-C/Swift specific 30 | *.hmap 31 | *.ipa 32 | *.dSYM.zip 33 | *.dSYM 34 | 35 | ## Playgrounds 36 | timeline.xctimeline 37 | playground.xcworkspace 38 | 39 | # Swift Package Manager 40 | # 41 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 42 | # Packages/ 43 | # Package.pins 44 | # Package.resolved 45 | .build/ 46 | .swiftpm/ 47 | 48 | # CocoaPods 49 | # 50 | # We recommend against adding the Pods directory to your .gitignore. However 51 | # you should judge for yourself, the pros and cons are mentioned at: 52 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 53 | # 54 | # Pods/ 55 | # 56 | # Add this line if you want to avoid checking in source code from the Xcode workspace 57 | # *.xcworkspace 58 | 59 | # Carthage 60 | # 61 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 62 | # Carthage/Checkouts 63 | 64 | Carthage/Build 65 | 66 | # fastlane 67 | # 68 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 69 | # screenshots whenever they are needed. 70 | # For more information about the recommended setup visit: 71 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 72 | 73 | fastlane/report.xml 74 | fastlane/Preview.html 75 | fastlane/screenshots/**/*.png 76 | fastlane/test_output 77 | 78 | # Code Injection 79 | # 80 | # After new code Injection tools there's a generated folder /iOSInjectionProject 81 | # https://github.com/johnno1962/injectionforxcode 82 | 83 | iOSInjectionProject/ 84 | 85 | # End of https://www.gitignore.io/api/osx,swift 86 | 87 | ### OSX ### 88 | # General 89 | .DS_Store 90 | .AppleDouble 91 | .LSOverride 92 | 93 | # Icon must end with two \r 94 | Icon 95 | 96 | # Thumbnails 97 | ._* 98 | 99 | # Files that might appear in the root of a volume 100 | .DocumentRevisions-V100 101 | .fseventsd 102 | .Spotlight-V100 103 | .TemporaryItems 104 | .Trashes 105 | .VolumeIcon.icns 106 | .com.apple.timemachine.donotpresent 107 | 108 | # Directories potentially created on remote AFP share 109 | .AppleDB 110 | .AppleDesktop 111 | Network Trash Folder 112 | Temporary Items 113 | .apdisk 114 | 115 | # custom 116 | tmp/ 117 | temp/ 118 | dist/ 119 | *.log 120 | *.xcframework 121 | Package.resolved -------------------------------------------------------------------------------- /Demo/Demo-iOS/Demo-iOS.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Demo/Demo-iOS/Demo-iOS.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Demo/Demo-iOS/Demo-iOS.xcodeproj/xcshareddata/xcschemes/Demo-iOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 43 | 45 | 51 | 52 | 53 | 54 | 60 | 62 | 68 | 69 | 70 | 71 | 73 | 74 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /Demo/Demo-iOS/Demo-iOS/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Demo/Demo-iOS/Demo-iOS/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "platform" : "ios", 6 | "size" : "1024x1024" 7 | } 8 | ], 9 | "info" : { 10 | "author" : "xcode", 11 | "version" : 1 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Demo/Demo-iOS/Demo-iOS/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Demo/Demo-iOS/Demo-iOS/ContentView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct ContentView: View { 4 | @ObservedObject var coordinator = MPVMetalPlayerView.Coordinator() 5 | @State var loading = false 6 | 7 | 8 | var body: some View { 9 | VStack { 10 | MPVMetalPlayerView(coordinator: coordinator) 11 | .play(URL(string: "https://github.com/mpvkit/video-test/raw/master/resources/HDR10_ToneMapping_Test_240_1000_nits.mp4")!) 12 | .onPropertyChange{ player, propertyName, propertyData in 13 | switch propertyName { 14 | case MPVProperty.pausedForCache: 15 | loading = propertyData as! Bool 16 | default: break 17 | } 18 | } 19 | } 20 | .overlay { 21 | VStack { 22 | Spacer() 23 | ScrollView(.horizontal) { 24 | HStack { 25 | Button { 26 | coordinator.play(URL(string: "https://vjs.zencdn.net/v/oceans.mp4")!) 27 | } label: { 28 | Text("h264").frame(width: 130, height: 100) 29 | } 30 | Button { 31 | coordinator.play(URL(string: "https://github.com/mpvkit/video-test/raw/master/resources/h265.mp4")!) 32 | } label: { 33 | Text("h265").frame(width: 130, height: 100) 34 | } 35 | Button { 36 | coordinator.play(URL(string: "https://github.com/mpvkit/video-test/raw/master/resources/pgs_subtitle.mkv")!) 37 | } label: { 38 | Text("subtitle").frame(width: 130, height: 100) 39 | } 40 | Button { 41 | coordinator.play(URL(string: "https://github.com/mpvkit/video-test/raw/master/resources/hdr.mkv")!) 42 | } label: { 43 | Text("HDR").frame(width: 130, height: 100) 44 | } 45 | Button { 46 | coordinator.play(URL(string: "https://github.com/mpvkit/video-test/raw/master/resources/DolbyVision_P5.mp4")!) 47 | } label: { 48 | Text("DV_P5").frame(width: 130, height: 100) 49 | } 50 | Button { 51 | coordinator.play(URL(string: "https://github.com/mpvkit/video-test/raw/master/resources/DolbyVision_P8.mp4")!) 52 | } label: { 53 | Text("DV_P8").frame(width: 130, height: 100) 54 | } 55 | } 56 | } 57 | } 58 | } 59 | .overlay(overlayView) 60 | .preferredColorScheme(.dark) 61 | } 62 | 63 | @ViewBuilder 64 | private var overlayView: some View { 65 | if loading { 66 | ProgressView() 67 | } else { 68 | EmptyView() 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Demo/Demo-iOS/Demo-iOS/Demo_iOSApp.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | @main 4 | struct Demo_iOSApp: App { 5 | var body: some Scene { 6 | WindowGroup { 7 | ContentView() 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Demo/Demo-iOS/Demo-iOS/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSAppTransportSecurity 6 | 7 | NSAllowsArbitraryLoads 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Demo/Demo-iOS/Demo-iOS/Player/MPVPlayerDelegate.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | @MainActor 4 | public protocol MPVPlayerDelegate: AnyObject { 5 | func propertyChange(mpv: OpaquePointer, propertyName: String, data: Any?) 6 | } 7 | -------------------------------------------------------------------------------- /Demo/Demo-iOS/Demo-iOS/Player/MPVProperty.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | struct MPVProperty { 4 | static let videoParamsColormatrix = "video-params/colormatrix" 5 | static let videoParamsColorlevels = "video-params/colorlevels" 6 | static let videoParamsPrimaries = "video-params/primaries" 7 | static let videoParamsGamma = "video-params/gamma" 8 | static let videoParamsSigPeak = "video-params/sig-peak" 9 | static let videoParamsSceneMaxR = "video-params/scene-max-r" 10 | static let videoParamsSceneMaxG = "video-params/scene-max-g" 11 | static let videoParamsSceneMaxB = "video-params/scene-max-b" 12 | static let pause = "pause" 13 | static let pausedForCache = "paused-for-cache" 14 | } 15 | -------------------------------------------------------------------------------- /Demo/Demo-iOS/Demo-iOS/Player/Metal/MPVMetalPlayerView.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import SwiftUI 3 | 4 | struct MPVMetalPlayerView: UIViewControllerRepresentable { 5 | @ObservedObject var coordinator: Coordinator 6 | 7 | func makeUIViewController(context: Context) -> some UIViewController { 8 | let mpv = MPVMetalViewController() 9 | mpv.playDelegate = coordinator 10 | mpv.playUrl = coordinator.playUrl 11 | 12 | context.coordinator.player = mpv 13 | return mpv 14 | } 15 | 16 | func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) { 17 | } 18 | 19 | public func makeCoordinator() -> Coordinator { 20 | coordinator 21 | } 22 | 23 | func play(_ url: URL) -> Self { 24 | coordinator.playUrl = url 25 | return self 26 | } 27 | 28 | func onPropertyChange(_ handler: @escaping (MPVMetalViewController, String, Any?) -> Void) -> Self { 29 | coordinator.onPropertyChange = handler 30 | return self 31 | } 32 | 33 | @MainActor 34 | public final class Coordinator: MPVPlayerDelegate, ObservableObject { 35 | weak var player: MPVMetalViewController? 36 | 37 | var playUrl : URL? 38 | var onPropertyChange: ((MPVMetalViewController, String, Any?) -> Void)? 39 | 40 | func play(_ url: URL) { 41 | player?.loadFile(url) 42 | } 43 | 44 | func propertyChange(mpv: OpaquePointer, propertyName: String, data: Any?) { 45 | guard let player else { return } 46 | 47 | self.onPropertyChange?(player, propertyName, data) 48 | } 49 | } 50 | } 51 | 52 | -------------------------------------------------------------------------------- /Demo/Demo-iOS/Demo-iOS/Player/Metal/MPVMetalViewController.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import UIKit 3 | import Libmpv 4 | 5 | // warning: metal API validation has been disabled to ignore crash when playing HDR videos. 6 | // Edit Scheme -> Run -> Diagnostics -> Metal API Validation -> Turn it off 7 | // https://github.com/KhronosGroup/MoltenVK/issues/2226 8 | final class MPVMetalViewController: UIViewController { 9 | var metalLayer = MetalLayer() 10 | var mpv: OpaquePointer! 11 | var playDelegate: MPVPlayerDelegate? 12 | lazy var queue = DispatchQueue(label: "mpv", qos: .userInitiated) 13 | 14 | var playUrl: URL? 15 | var hdrAvailable : Bool { 16 | let maxEDRRange = view.window?.screen.potentialEDRHeadroom ?? 1.0 17 | let sigPeak = getDouble(MPVProperty.videoParamsSigPeak) 18 | // display screen support HDR and current playing HDR video 19 | return maxEDRRange > 1.0 && sigPeak > 1.0 20 | } 21 | var hdrEnabled = false { 22 | didSet { 23 | // FIXME: target-colorspace-hint does not support being changed at runtime. 24 | // this option should be set as early as possible otherwise can cause issues 25 | // not recommended to use this way. 26 | if hdrEnabled { 27 | checkError(mpv_set_option_string(mpv, "target-colorspace-hint", "yes")) 28 | } else { 29 | checkError(mpv_set_option_string(mpv, "target-colorspace-hint", "no")) 30 | } 31 | } 32 | } 33 | 34 | override func viewDidLoad() { 35 | super.viewDidLoad() 36 | 37 | metalLayer.frame = view.frame 38 | print(view.bounds) 39 | print(view.frame) 40 | metalLayer.contentsScale = UIScreen.main.nativeScale 41 | metalLayer.framebufferOnly = true 42 | metalLayer.backgroundColor = UIColor.black.cgColor 43 | 44 | view.layer.addSublayer(metalLayer) 45 | 46 | setupMpv() 47 | 48 | if let url = playUrl { 49 | loadFile(url) 50 | } 51 | } 52 | 53 | override func viewDidLayoutSubviews() { 54 | super.viewDidLayoutSubviews() 55 | 56 | metalLayer.frame = view.frame 57 | } 58 | 59 | func setupMpv() { 60 | mpv = mpv_create() 61 | if mpv == nil { 62 | print("failed creating context\n") 63 | exit(1) 64 | } 65 | 66 | // https://mpv.io/manual/stable/#options 67 | #if DEBUG 68 | checkError(mpv_request_log_messages(mpv, "debug")) 69 | #else 70 | checkError(mpv_request_log_messages(mpv, "no")) 71 | #endif 72 | #if os(macOS) 73 | checkError(mpv_set_option_string(mpv, "input-media-keys", "yes")) 74 | #endif 75 | checkError(mpv_set_option(mpv, "wid", MPV_FORMAT_INT64, &metalLayer)) 76 | checkError(mpv_set_option_string(mpv, "subs-match-os-language", "yes")) 77 | checkError(mpv_set_option_string(mpv, "subs-fallback", "yes")) 78 | checkError(mpv_set_option_string(mpv, "vo", "gpu-next")) 79 | checkError(mpv_set_option_string(mpv, "gpu-api", "vulkan")) 80 | checkError(mpv_set_option_string(mpv, "hwdec", "videotoolbox")) 81 | checkError(mpv_set_option_string(mpv, "video-rotate", "no")) 82 | checkError(mpv_set_option_string(mpv, "ytdl", "no")) 83 | // checkError(mpv_set_option_string(mpv, "target-colorspace-hint", "yes")) // HDR passthrough 84 | // checkError(mpv_set_option_string(mpv, "tone-mapping-visualize", "yes")) // only for debugging purposes 85 | // checkError(mpv_set_option_string(mpv, "profile", "fast")) // can fix frame drop in poor device when play 4k 86 | 87 | 88 | checkError(mpv_initialize(mpv)) 89 | 90 | mpv_observe_property(mpv, 0, MPVProperty.videoParamsSigPeak, MPV_FORMAT_DOUBLE) 91 | mpv_observe_property(mpv, 0, MPVProperty.pausedForCache, MPV_FORMAT_FLAG) 92 | mpv_set_wakeup_callback(self.mpv, { (ctx) in 93 | let client = unsafeBitCast(ctx, to: MPVMetalViewController.self) 94 | client.readEvents() 95 | }, UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque())) 96 | } 97 | 98 | 99 | func loadFile( 100 | _ url: URL 101 | ) { 102 | var args = [url.absoluteString] 103 | var options = [String]() 104 | 105 | args.append("replace") 106 | 107 | if !options.isEmpty { 108 | args.append(options.joined(separator: ",")) 109 | } 110 | 111 | command("loadfile", args: args) 112 | } 113 | 114 | func togglePause() { 115 | getFlag(MPVProperty.pause) ? play() : pause() 116 | } 117 | 118 | func play() { 119 | setFlag(MPVProperty.pause, false) 120 | } 121 | 122 | func pause() { 123 | setFlag(MPVProperty.pause, true) 124 | } 125 | 126 | private func getDouble(_ name: String) -> Double { 127 | guard mpv != nil else { return 0.0 } 128 | var data = Double() 129 | mpv_get_property(mpv, name, MPV_FORMAT_DOUBLE, &data) 130 | return data 131 | } 132 | 133 | private func getString(_ name: String) -> String? { 134 | guard mpv != nil else { return nil } 135 | let cstr = mpv_get_property_string(mpv, name) 136 | let str: String? = cstr == nil ? nil : String(cString: cstr!) 137 | mpv_free(cstr) 138 | return str 139 | } 140 | 141 | private func getFlag(_ name: String) -> Bool { 142 | var data = Int64() 143 | mpv_get_property(mpv, name, MPV_FORMAT_FLAG, &data) 144 | return data > 0 145 | } 146 | 147 | private func setFlag(_ name: String, _ flag: Bool) { 148 | guard mpv != nil else { return } 149 | var data: Int = flag ? 1 : 0 150 | mpv_set_property(mpv, name, MPV_FORMAT_FLAG, &data) 151 | } 152 | 153 | 154 | func command( 155 | _ command: String, 156 | args: [String?] = [], 157 | checkForErrors: Bool = true, 158 | returnValueCallback: ((Int32) -> Void)? = nil 159 | ) { 160 | guard mpv != nil else { 161 | return 162 | } 163 | var cargs = makeCArgs(command, args).map { $0.flatMap { UnsafePointer(strdup($0)) } } 164 | defer { 165 | for ptr in cargs where ptr != nil { 166 | free(UnsafeMutablePointer(mutating: ptr!)) 167 | } 168 | } 169 | //print("\(command) -- \(args)") 170 | let returnValue = mpv_command(mpv, &cargs) 171 | if checkForErrors { 172 | checkError(returnValue) 173 | } 174 | if let cb = returnValueCallback { 175 | cb(returnValue) 176 | } 177 | } 178 | 179 | private func makeCArgs(_ command: String, _ args: [String?]) -> [String?] { 180 | if !args.isEmpty, args.last == nil { 181 | fatalError("Command do not need a nil suffix") 182 | } 183 | 184 | var strArgs = args 185 | strArgs.insert(command, at: 0) 186 | strArgs.append(nil) 187 | 188 | return strArgs 189 | } 190 | 191 | func readEvents() { 192 | queue.async { [weak self] in 193 | guard let self else { return } 194 | 195 | while self.mpv != nil { 196 | let event = mpv_wait_event(self.mpv, 0) 197 | if event?.pointee.event_id == MPV_EVENT_NONE { 198 | break 199 | } 200 | 201 | switch event!.pointee.event_id { 202 | case MPV_EVENT_PROPERTY_CHANGE: 203 | let dataOpaquePtr = OpaquePointer(event!.pointee.data) 204 | if let property = UnsafePointer(dataOpaquePtr)?.pointee { 205 | let propertyName = String(cString: property.name) 206 | switch propertyName { 207 | case MPVProperty.pausedForCache: 208 | let buffering = UnsafePointer(OpaquePointer(property.data))?.pointee ?? true 209 | DispatchQueue.main.async { 210 | self.playDelegate?.propertyChange(mpv: self.mpv, propertyName: propertyName, data: buffering) 211 | } 212 | default: break 213 | } 214 | } 215 | case MPV_EVENT_SHUTDOWN: 216 | print("event: shutdown\n"); 217 | mpv_terminate_destroy(mpv); 218 | mpv = nil; 219 | break; 220 | case MPV_EVENT_LOG_MESSAGE: 221 | let msg = UnsafeMutablePointer(OpaquePointer(event!.pointee.data)) 222 | print("[\(String(cString: (msg!.pointee.prefix)!))] \(String(cString: (msg!.pointee.level)!)): \(String(cString: (msg!.pointee.text)!))", terminator: "") 223 | default: 224 | let eventName = mpv_event_name(event!.pointee.event_id ) 225 | print("event: \(String(cString: (eventName)!))"); 226 | } 227 | 228 | } 229 | } 230 | } 231 | 232 | 233 | private func checkError(_ status: CInt) { 234 | if status < 0 { 235 | print("MPV API error: \(String(cString: mpv_error_string(status)))\n") 236 | } 237 | } 238 | 239 | } 240 | -------------------------------------------------------------------------------- /Demo/Demo-iOS/Demo-iOS/Player/Metal/MetalLayer.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import UIKit 3 | 4 | class MetalLayer: CAMetalLayer { 5 | 6 | // workaround for a MoltenVK that sets the drawableSize to 1x1 to forcefully complete 7 | // the presentation, this causes flicker and the drawableSize possibly staying at 1x1 8 | // https://github.com/mpv-player/mpv/pull/13651 9 | override var drawableSize: CGSize { 10 | get { return super.drawableSize } 11 | set { 12 | if Int(newValue.width) > 1 && Int(newValue.height) > 1 { 13 | super.drawableSize = newValue 14 | } 15 | } 16 | } 17 | 18 | // Hack for fix [target-colorspace-hint] option: 19 | // Update wantsExtendedDynamicRangeContent need run in main thread to activate screen EDR mode, other thread can't activate 20 | override var wantsExtendedDynamicRangeContent: Bool { 21 | get { 22 | return super.wantsExtendedDynamicRangeContent 23 | } 24 | set { 25 | if Thread.isMainThread { 26 | super.wantsExtendedDynamicRangeContent = newValue 27 | } else { 28 | DispatchQueue.main.sync { 29 | super.wantsExtendedDynamicRangeContent = newValue 30 | } 31 | } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Demo/Demo-iOS/Demo-iOS/Player/OpenGL/MPVPlayerView.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import SwiftUI 3 | 4 | struct MPVPlayerView: UIViewControllerRepresentable { 5 | @ObservedObject var coordinator: Coordinator 6 | 7 | func makeUIViewController(context: Context) -> some UIViewController { 8 | let mpv = MPVViewController() 9 | mpv.playDelegate = coordinator 10 | mpv.playUrl = coordinator.playUrl 11 | 12 | context.coordinator.player = mpv 13 | return mpv 14 | } 15 | 16 | func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) { 17 | } 18 | 19 | public func makeCoordinator() -> Coordinator { 20 | coordinator 21 | } 22 | 23 | func play(_ url: URL) -> Self { 24 | coordinator.playUrl = url 25 | return self 26 | } 27 | 28 | func onPropertyChange(_ handler: @escaping (MPVViewController, String, Any?) -> Void) -> Self { 29 | coordinator.onPropertyChange = handler 30 | return self 31 | } 32 | 33 | @MainActor 34 | public final class Coordinator: MPVPlayerDelegate, ObservableObject { 35 | weak var player: MPVViewController? 36 | 37 | var playUrl : URL? 38 | var onPropertyChange: ((MPVViewController, String, Any?) -> Void)? 39 | 40 | func play(_ url: URL) { 41 | player?.loadFile(url) 42 | } 43 | 44 | func propertyChange(mpv: OpaquePointer, propertyName: String, data: Any?) { 45 | guard let player else { return } 46 | 47 | self.onPropertyChange?(player, propertyName, data) 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Demo/Demo-iOS/Demo-iOS/Player/OpenGL/MPVViewController.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import GLKit 3 | import Libmpv 4 | 5 | class MPVViewController: GLKViewController { 6 | var mpv: OpaquePointer! 7 | var mpvGL: OpaquePointer! 8 | var playDelegate: MPVPlayerDelegate? 9 | var queue: DispatchQueue = DispatchQueue(label: "mpv", qos: .userInteractive) 10 | private var defaultFBO: GLint = -1 11 | var playUrl: URL? 12 | 13 | 14 | override func viewDidLoad() { 15 | super.viewDidLoad() 16 | 17 | self.setupContext() 18 | self.setupMpv() 19 | 20 | if let url = playUrl { 21 | self.loadFile(url) 22 | } 23 | } 24 | 25 | func setupContext() { 26 | let context = EAGLContext(api: .openGLES2)! 27 | if context == nil { 28 | print("create context fail ...") 29 | return 30 | } 31 | let isSuccess = EAGLContext.setCurrent(context) 32 | if !isSuccess { 33 | print("setup context fail") 34 | } 35 | 36 | let glkView = self.view as! GLKView 37 | glkView.context = context 38 | } 39 | 40 | func setupMpv() { 41 | mpv = mpv_create() 42 | if mpv == nil { 43 | print("failed creating context\n") 44 | exit(1) 45 | } 46 | 47 | // https://mpv.io/manual/stable/#options 48 | #if DEBUG 49 | checkError(mpv_request_log_messages(mpv, "debug")) 50 | #else 51 | checkError(mpv_request_log_messages(mpv, "no")) 52 | #endif 53 | #if os(macOS) 54 | checkError(mpv_set_option_string(mpv, "input-media-keys", "yes")) 55 | #endif 56 | checkError(mpv_set_option_string(mpv, "subs-match-os-language", "yes")) 57 | checkError(mpv_set_option_string(mpv, "subs-fallback", "yes")) 58 | checkError(mpv_set_option_string(mpv, "hwdec", machine == "x86_64" ? "no" : "auto-safe")) 59 | checkError(mpv_set_option_string(mpv, "vo", "libmpv")) 60 | 61 | checkError(mpv_initialize(mpv)) 62 | 63 | let api = UnsafeMutableRawPointer(mutating: (MPV_RENDER_API_TYPE_OPENGL as NSString).utf8String) 64 | var initParams = mpv_opengl_init_params( 65 | get_proc_address: { 66 | (ctx, name) in 67 | return MPVViewController.getProcAddress(ctx, name) 68 | }, 69 | get_proc_address_ctx: nil 70 | ) 71 | 72 | withUnsafeMutablePointer(to: &initParams) { initParams in 73 | var params = [ 74 | mpv_render_param(type: MPV_RENDER_PARAM_API_TYPE, data: api), 75 | mpv_render_param(type: MPV_RENDER_PARAM_OPENGL_INIT_PARAMS, data: initParams), 76 | mpv_render_param() 77 | ] 78 | 79 | if mpv_render_context_create(&mpvGL, mpv, ¶ms) < 0 { 80 | puts("failed to initialize mpv GL context") 81 | exit(1) 82 | } 83 | 84 | mpv_render_context_set_update_callback( 85 | mpvGL, 86 | mpvGLUpdate, 87 | UnsafeMutableRawPointer(Unmanaged.passUnretained(self.view).toOpaque()) 88 | ) 89 | 90 | } 91 | 92 | mpv_observe_property(mpv, 0, MPVProperty.videoParamsSigPeak, MPV_FORMAT_DOUBLE) 93 | mpv_observe_property(mpv, 0, MPVProperty.pausedForCache, MPV_FORMAT_FLAG) 94 | mpv_set_wakeup_callback(self.mpv, { (ctx) in 95 | let client = unsafeBitCast(ctx, to: MPVViewController.self) 96 | client.readEvents() 97 | }, UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque())) 98 | } 99 | 100 | 101 | func loadFile( 102 | _ url: URL 103 | ) { 104 | var args = [url.absoluteString] 105 | var options = [String]() 106 | 107 | args.append("replace") 108 | 109 | if !options.isEmpty { 110 | args.append(options.joined(separator: ",")) 111 | } 112 | 113 | command("loadfile", args: args) 114 | } 115 | 116 | func togglePause() { 117 | getFlag(MPVProperty.pause) ? play() : pause() 118 | } 119 | 120 | func play() { 121 | setFlag(MPVProperty.pause, false) 122 | } 123 | 124 | func pause() { 125 | setFlag(MPVProperty.pause, true) 126 | } 127 | 128 | private func getFlag(_ name: String) -> Bool { 129 | var data = Int64() 130 | mpv_get_property(mpv, name, MPV_FORMAT_FLAG, &data) 131 | return data > 0 132 | } 133 | 134 | private func setFlag(_ name: String, _ flag: Bool) { 135 | guard mpv != nil else { return } 136 | var data: Int = flag ? 1 : 0 137 | mpv_set_property(mpv, name, MPV_FORMAT_FLAG, &data) 138 | } 139 | 140 | func command( 141 | _ command: String, 142 | args: [String?] = [], 143 | checkForErrors: Bool = true, 144 | returnValueCallback: ((Int32) -> Void)? = nil 145 | ) { 146 | guard mpv != nil else { 147 | return 148 | } 149 | var cargs = makeCArgs(command, args).map { $0.flatMap { UnsafePointer(strdup($0)) } } 150 | defer { 151 | for ptr in cargs where ptr != nil { 152 | free(UnsafeMutablePointer(mutating: ptr!)) 153 | } 154 | } 155 | //print("\(command) -- \(args)") 156 | let returnValue = mpv_command(mpv, &cargs) 157 | if checkForErrors { 158 | checkError(returnValue) 159 | } 160 | if let cb = returnValueCallback { 161 | cb(returnValue) 162 | } 163 | } 164 | 165 | private func makeCArgs(_ command: String, _ args: [String?]) -> [String?] { 166 | if !args.isEmpty, args.last == nil { 167 | fatalError("Command do not need a nil suffix") 168 | } 169 | 170 | var strArgs = args 171 | strArgs.insert(command, at: 0) 172 | strArgs.append(nil) 173 | 174 | return strArgs 175 | } 176 | 177 | 178 | func readEvents() { 179 | queue.async { [self] in 180 | while self.mpv != nil { 181 | let event = mpv_wait_event(self.mpv, 0) 182 | if event!.pointee.event_id == MPV_EVENT_NONE { 183 | break 184 | } 185 | switch event!.pointee.event_id { 186 | case MPV_EVENT_PROPERTY_CHANGE: 187 | let dataOpaquePtr = OpaquePointer(event!.pointee.data) 188 | if let property = UnsafePointer(dataOpaquePtr)?.pointee { 189 | let propertyName = String(cString: property.name) 190 | switch propertyName { 191 | case MPVProperty.pausedForCache: 192 | let buffering = UnsafePointer(OpaquePointer(property.data))?.pointee ?? true 193 | DispatchQueue.main.async { 194 | self.playDelegate?.propertyChange(mpv: self.mpv, propertyName: propertyName, data: buffering) 195 | } 196 | default: break 197 | } 198 | } 199 | case MPV_EVENT_SHUTDOWN: 200 | mpv_render_context_free(mpvGL); 201 | mpv_terminate_destroy(mpv); 202 | mpv = nil; 203 | print("event: shutdown\n"); 204 | break; 205 | case MPV_EVENT_LOG_MESSAGE: 206 | let msg = UnsafeMutablePointer(OpaquePointer(event!.pointee.data)) 207 | print("[\(String(cString: (msg!.pointee.prefix)!))] \(String(cString: (msg!.pointee.level)!)): \(String(cString: (msg!.pointee.text)!))", terminator: "") 208 | default: 209 | let eventName = mpv_event_name(event!.pointee.event_id ) 210 | print("event: \(String(cString: (eventName)!))"); 211 | } 212 | } 213 | } 214 | } 215 | 216 | 217 | private func checkError(_ status: CInt) { 218 | if status < 0 { 219 | print("MPV API error: \(String(cString: mpv_error_string(status)))\n") 220 | } 221 | } 222 | 223 | 224 | private var machine: String { 225 | var systeminfo = utsname() 226 | uname(&systeminfo) 227 | return withUnsafeBytes(of: &systeminfo.machine) { bufPtr -> String in 228 | let data = Data(bufPtr) 229 | if let lastIndex = data.lastIndex(where: { $0 != 0 }) { 230 | return String(data: data[0 ... lastIndex], encoding: .isoLatin1)! 231 | } else { 232 | return String(data: data, encoding: .isoLatin1)! 233 | } 234 | } 235 | } 236 | 237 | 238 | override func glkView(_ view: GLKView, drawIn rect: CGRect) { 239 | guard let mpvGL else { 240 | return 241 | } 242 | 243 | // fill black background 244 | glClearColor(0, 0, 0, 0) 245 | glClear(UInt32(GL_COLOR_BUFFER_BIT)) 246 | 247 | glGetIntegerv(UInt32(GL_FRAMEBUFFER_BINDING), &defaultFBO) 248 | 249 | var dims: [GLint] = [0, 0, 0, 0] 250 | glGetIntegerv(GLenum(GL_VIEWPORT), &dims) 251 | 252 | var data = mpv_opengl_fbo( 253 | fbo: Int32(defaultFBO), 254 | w: Int32(dims[2]), 255 | h: Int32(dims[3]), 256 | internal_format: 0 257 | ) 258 | 259 | var flip: CInt = 1 260 | withUnsafeMutablePointer(to: &flip) { flip in 261 | withUnsafeMutablePointer(to: &data) { data in 262 | var params = [ 263 | mpv_render_param(type: MPV_RENDER_PARAM_OPENGL_FBO, data: data), 264 | mpv_render_param(type: MPV_RENDER_PARAM_FLIP_Y, data: flip), 265 | mpv_render_param() 266 | ] 267 | mpv_render_context_render(mpvGL, ¶ms) 268 | } 269 | } 270 | 271 | } 272 | 273 | 274 | private static func getProcAddress(_: UnsafeMutableRawPointer?, _ name: UnsafePointer?) -> UnsafeMutableRawPointer? { 275 | let symbolName = CFStringCreateWithCString(kCFAllocatorDefault, name, CFStringBuiltInEncodings.ASCII.rawValue) 276 | let identifier = CFBundleGetBundleWithIdentifier("com.apple.opengles" as CFString) 277 | 278 | return CFBundleGetFunctionPointerForName(identifier, symbolName) 279 | } 280 | 281 | } 282 | 283 | 284 | 285 | private func mpvGLUpdate(_ ctx: UnsafeMutableRawPointer?) { 286 | let glView = unsafeBitCast(ctx, to: GLKView.self) 287 | 288 | DispatchQueue.main.async { 289 | glView.display() 290 | } 291 | } 292 | -------------------------------------------------------------------------------- /Demo/Demo-iOS/Demo-iOS/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Demo/Demo-macOS/Demo-macOS.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Demo/Demo-macOS/Demo-macOS.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Demo/Demo-macOS/Demo-macOS.xcodeproj/xcshareddata/xcschemes/Demo-macOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 43 | 45 | 51 | 52 | 53 | 54 | 60 | 62 | 68 | 69 | 70 | 71 | 73 | 74 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /Demo/Demo-macOS/Demo-macOS/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Demo/Demo-macOS/Demo-macOS/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "scale" : "1x", 6 | "size" : "16x16" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "scale" : "2x", 11 | "size" : "16x16" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "scale" : "1x", 16 | "size" : "32x32" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "scale" : "2x", 21 | "size" : "32x32" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "scale" : "1x", 26 | "size" : "128x128" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "scale" : "2x", 31 | "size" : "128x128" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "scale" : "1x", 36 | "size" : "256x256" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "scale" : "2x", 41 | "size" : "256x256" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "scale" : "1x", 46 | "size" : "512x512" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "scale" : "2x", 51 | "size" : "512x512" 52 | } 53 | ], 54 | "info" : { 55 | "author" : "xcode", 56 | "version" : 1 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Demo/Demo-macOS/Demo-macOS/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Demo/Demo-macOS/Demo-macOS/ContentView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct ContentView: View { 4 | @ObservedObject var coordinator = MPVMetalPlayerView.Coordinator() 5 | @State var hdrAvailable = false 6 | @State var tonemappingVisualizeEnabled = false 7 | @State var showControlOverlay = false 8 | @State var loading = false 9 | 10 | var body: some View { 11 | VStack { 12 | MPVMetalPlayerView(coordinator: coordinator) 13 | .play(URL(string: "https://github.com/mpvkit/video-test/raw/master/resources/HDR10_ToneMapping_Test_240_1000_nits.mp4")!) 14 | .onPropertyChange{ player, propertyName, propertyData in 15 | switch propertyName { 16 | case MPVProperty.videoParamsSigPeak: 17 | coordinator.hdrAvailable = player.hdrAvailable 18 | case MPVProperty.pausedForCache: 19 | loading = propertyData as! Bool 20 | case "edr": 21 | if let edrRange = propertyData as? CGFloat { 22 | coordinator.edrRange = String(format: "%.1f", edrRange) 23 | } 24 | default: break 25 | } 26 | } 27 | } 28 | .overlay { 29 | HStack { 30 | VStack(alignment: .leading, spacing: 12) { 31 | Button { 32 | coordinator.pause.toggle() 33 | } label: { 34 | Text(coordinator.pause ? "play" : "pause").frame(maxWidth: .infinity) 35 | } 36 | Divider() 37 | Button { 38 | coordinator.play(URL(string: "https://vjs.zencdn.net/v/oceans.mp4")!) 39 | } label: { 40 | Text("h264").frame(maxWidth: .infinity) 41 | } 42 | Button { 43 | coordinator.play(URL(string: "https://github.com/mpvkit/video-test/raw/master/resources/h265.mp4")!) 44 | } label: { 45 | Text("h265").frame(maxWidth: .infinity) 46 | } 47 | Button { 48 | coordinator.play(URL(string: "https://github.com/mpvkit/video-test/raw/master/resources/pgs_subtitle.mkv")!) 49 | } label: { 50 | Text("subtitle").frame(maxWidth: .infinity) 51 | } 52 | Button { 53 | coordinator.play(URL(string: "https://github.com/mpvkit/video-test/raw/master/resources/hdr.mkv")!) 54 | } label: { 55 | Text("HDR").frame(maxWidth: .infinity) 56 | } 57 | Button { 58 | coordinator.play(URL(string: "https://github.com/mpvkit/video-test/raw/master/resources/HDR10+.mp4")!) 59 | } label: { 60 | Text("HDR10+").frame(maxWidth: .infinity) 61 | } 62 | Button { 63 | coordinator.play(URL(string: "https://github.com/mpvkit/video-test/raw/master/resources/DolbyVision_P5.mp4")!) 64 | } label: { 65 | Text("DV_P5").frame(maxWidth: .infinity) 66 | } 67 | Button { 68 | coordinator.play(URL(string: "https://github.com/mpvkit/video-test/raw/master/resources/DolbyVision_P8.mp4")!) 69 | } label: { 70 | Text("DV_P8").frame(maxWidth: .infinity) 71 | } 72 | Divider() 73 | 74 | HStack() { 75 | Text("HDR") 76 | Spacer() 77 | Toggle("", isOn: $coordinator.hdrEnabled) 78 | .disabled(!coordinator.hdrAvailable) 79 | .toggleStyle(.switch) 80 | } 81 | HStack() { 82 | Text("EDR") 83 | Spacer() 84 | Text(coordinator.edrRange) 85 | } 86 | Spacer() 87 | } 88 | .padding(.horizontal, 18) 89 | .padding(.top, 40) 90 | .background(.ultraThickMaterial) 91 | .frame(width: 200) 92 | Spacer() 93 | } 94 | .opacity(showControlOverlay ? 1 : 0) 95 | } 96 | .onHover { hover in 97 | showControlOverlay = hover 98 | } 99 | .overlay(overlayView) 100 | .preferredColorScheme(.dark) 101 | .ignoresSafeArea() 102 | } 103 | 104 | @ViewBuilder 105 | private var overlayView: some View { 106 | if loading { 107 | ProgressView() 108 | } else { 109 | EmptyView() 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /Demo/Demo-macOS/Demo-macOS/Demo_macOS.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-only 8 | 9 | com.apple.security.network.client 10 | 11 | com.apple.security.network.server 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Demo/Demo-macOS/Demo-macOS/Demo_macOSApp.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | @main 4 | struct Demo_macOSApp: App { 5 | var body: some Scene { 6 | WindowGroup { 7 | ContentView() 8 | } 9 | .windowStyle(.hiddenTitleBar) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Demo/Demo-macOS/Demo-macOS/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSAppTransportSecurity 6 | 7 | NSAllowsArbitraryLoads 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Demo/Demo-macOS/Demo-macOS/Player/MPVPlayerDelegate.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | @MainActor 4 | public protocol MPVPlayerDelegate: AnyObject { 5 | func propertyChange(mpv: OpaquePointer, propertyName: String, data: Any?) 6 | } 7 | -------------------------------------------------------------------------------- /Demo/Demo-macOS/Demo-macOS/Player/MPVProperty.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | struct MPVProperty { 4 | static let videoParamsColormatrix = "video-params/colormatrix" 5 | static let videoParamsColorlevels = "video-params/colorlevels" 6 | static let videoParamsPrimaries = "video-params/primaries" 7 | static let videoParamsGamma = "video-params/gamma" 8 | static let videoParamsSigPeak = "video-params/sig-peak" 9 | static let videoParamsSceneMaxR = "video-params/scene-max-r" 10 | static let videoParamsSceneMaxG = "video-params/scene-max-g" 11 | static let videoParamsSceneMaxB = "video-params/scene-max-b" 12 | static let duration = "duration" 13 | static let timePos = "time-pos" 14 | static let path = "path" 15 | static let pause = "pause" 16 | static let pausedForCache = "paused-for-cache" 17 | } 18 | -------------------------------------------------------------------------------- /Demo/Demo-macOS/Demo-macOS/Player/Metal/MPVMetalPlayerView.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import SwiftUI 3 | 4 | struct MPVMetalPlayerView: NSViewControllerRepresentable { 5 | @ObservedObject var coordinator: Coordinator 6 | 7 | func makeNSViewController(context: Context) -> some NSViewController { 8 | let mpv = MPVMetalViewController() 9 | mpv.playDelegate = coordinator 10 | mpv.playUrl = coordinator.playUrl 11 | 12 | context.coordinator.player = mpv 13 | return mpv 14 | } 15 | 16 | func updateNSViewController(_ nsViewController: NSViewControllerType, context: Context) { 17 | } 18 | 19 | public func makeCoordinator() -> Coordinator { 20 | coordinator 21 | } 22 | 23 | func play(_ url: URL) -> Self { 24 | coordinator.playUrl = url 25 | return self 26 | } 27 | 28 | func onPropertyChange(_ handler: @escaping (MPVMetalViewController, String, Any?) -> Void) -> Self { 29 | coordinator.onPropertyChange = handler 30 | return self 31 | } 32 | 33 | @MainActor 34 | public final class Coordinator: MPVPlayerDelegate, ObservableObject { 35 | weak var player: MPVMetalViewController? 36 | 37 | @Published var pause : Bool = false { 38 | didSet { 39 | if pause { 40 | self.player?.pause() 41 | } else { 42 | self.player?.play() 43 | } 44 | } 45 | } 46 | 47 | @Published var hdrEnabled : Bool = false { 48 | didSet { 49 | self.player?.hdrEnabled = hdrEnabled 50 | } 51 | } 52 | 53 | @Published var hdrAvailable : Bool = false 54 | @Published var edrRange : String = "1.0" 55 | 56 | var playUrl : URL? 57 | var onPropertyChange: ((MPVMetalViewController, String, Any?) -> Void)? 58 | 59 | func play(_ url: URL) { 60 | player?.loadFile(url) 61 | self.pause = false 62 | } 63 | 64 | func propertyChange(mpv: OpaquePointer, propertyName: String, data: Any?) { 65 | guard let player else { return } 66 | self.onPropertyChange?(player, propertyName, data) 67 | } 68 | } 69 | } 70 | 71 | -------------------------------------------------------------------------------- /Demo/Demo-macOS/Demo-macOS/Player/Metal/MPVMetalViewController.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import AppKit 3 | import Libmpv 4 | 5 | // warning: metal API validation has been disabled to ignore crash when playing HDR videos. 6 | // Edit Scheme -> Run -> Diagnostics -> Metal API Validation -> Turn it off 7 | // https://github.com/KhronosGroup/MoltenVK/issues/2226 8 | final class MPVMetalViewController: NSViewController { 9 | var metalLayer = MetalLayer() 10 | var mpv: OpaquePointer! 11 | var playDelegate: MPVPlayerDelegate? 12 | var edrRange: CGFloat? 13 | lazy var queue = DispatchQueue(label: "mpv", qos: .userInitiated) 14 | 15 | var playUrl: URL? 16 | var hdrAvailable : Bool { 17 | let maxEDRRange = NSScreen.main?.maximumPotentialExtendedDynamicRangeColorComponentValue ?? 1.0 18 | let sigPeak = getDouble(MPVProperty.videoParamsSigPeak) 19 | // display screen support HDR and current playing HDR video 20 | return maxEDRRange > 1.0 && sigPeak > 1.0 21 | } 22 | var hdrEnabled = false { 23 | didSet { 24 | // FIXME: target-colorspace-hint does not support being changed at runtime. 25 | // this option should be set when mpv init otherwise can cause player slow and hangs. 26 | // not recommended to use this way. 27 | if hdrEnabled { 28 | checkError(mpv_set_option_string(mpv, "target-colorspace-hint", "yes")) 29 | } else { 30 | checkError(mpv_set_option_string(mpv, "target-colorspace-hint", "no")) 31 | } 32 | } 33 | } 34 | 35 | override func loadView() { 36 | self.view = NSView(frame: .init(x: 0, y: 0, width: NSScreen.main!.frame.width, height: NSScreen.main!.frame.height)) 37 | self.view.wantsLayer = true 38 | } 39 | 40 | override func viewDidLoad() { 41 | super.viewDidLoad() 42 | 43 | metalLayer.frame = view.frame 44 | metalLayer.contentsScale = NSScreen.main!.backingScaleFactor 45 | metalLayer.framebufferOnly = true 46 | metalLayer.backgroundColor = NSColor.black.cgColor 47 | view.layer = metalLayer 48 | view.wantsLayer = true 49 | 50 | setupMpv() 51 | 52 | if let url = playUrl { 53 | loadFile(url) 54 | } 55 | 56 | // observer EDR range value change 57 | NotificationCenter.default.addObserver( 58 | forName: NSApplication.didChangeScreenParametersNotification, 59 | object: nil, 60 | queue: .main 61 | ) { [weak self] value in 62 | guard let self = self else { return } 63 | 64 | if let screen = NSScreen.screens.first { 65 | let maxRange = screen.maximumExtendedDynamicRangeColorComponentValue 66 | DispatchQueue.main.async { 67 | self.playDelegate?.propertyChange(mpv: self.mpv, propertyName: "edr", data: maxRange) 68 | } 69 | } 70 | } 71 | 72 | } 73 | 74 | override func viewDidLayout() { 75 | super.viewDidLayout() 76 | 77 | if let window = view.window { 78 | let scale = window.screen!.backingScaleFactor 79 | let layerSize = view.bounds.size 80 | 81 | metalLayer.frame = CGRect(x: 0, y: 0, width: layerSize.width, height: layerSize.height) 82 | metalLayer.drawableSize = CGSize(width: layerSize.width * scale, height: layerSize.height * scale) 83 | } 84 | } 85 | 86 | func setupMpv(hdrPass : Bool = false) { 87 | mpv = mpv_create() 88 | if mpv == nil { 89 | print("failed creating context\n") 90 | exit(1) 91 | } 92 | 93 | // https://mpv.io/manual/stable/#options 94 | #if DEBUG 95 | checkError(mpv_request_log_messages(mpv, "debug")) 96 | #else 97 | checkError(mpv_request_log_messages(mpv, "no")) 98 | #endif 99 | #if os(macOS) 100 | checkError(mpv_set_option_string(mpv, "input-media-keys", "yes")) 101 | #endif 102 | checkError(mpv_set_option(mpv, "wid", MPV_FORMAT_INT64, &metalLayer)) 103 | checkError(mpv_set_option_string(mpv, "subs-match-os-language", "yes")) 104 | checkError(mpv_set_option_string(mpv, "subs-fallback", "yes")) 105 | checkError(mpv_set_option_string(mpv, "vo", "gpu-next")) 106 | checkError(mpv_set_option_string(mpv, "gpu-api", "vulkan")) 107 | checkError(mpv_set_option_string(mpv, "hwdec", "videotoolbox")) 108 | checkError(mpv_set_option_string(mpv, "ytdl", "no")) 109 | // checkError(mpv_set_option_string(mpv, "target-colorspace-hint", "yes")) // HDR passthrough 110 | // checkError(mpv_set_option_string(mpv, "tone-mapping-visualize", "yes")) // only for debugging purposes 111 | // checkError(mpv_set_option_string(mpv, "profile", "fast")) // can fix frame drop in poor device when play 4k 112 | 113 | 114 | checkError(mpv_initialize(mpv)) 115 | 116 | mpv_observe_property(mpv, 0, MPVProperty.videoParamsSigPeak, MPV_FORMAT_DOUBLE) 117 | mpv_observe_property(mpv, 0, MPVProperty.videoParamsColormatrix, MPV_FORMAT_STRING) 118 | mpv_observe_property(mpv, 0, MPVProperty.pausedForCache, MPV_FORMAT_FLAG) 119 | mpv_set_wakeup_callback(self.mpv, { (ctx) in 120 | let client = unsafeBitCast(ctx, to: MPVMetalViewController.self) 121 | client.readEvents() 122 | }, UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque())) 123 | } 124 | 125 | 126 | func loadFile( 127 | _ url: URL, 128 | time: Double? = nil 129 | ) { 130 | var args = [url.absoluteString] 131 | var options = [String]() 132 | 133 | args.append("replace") 134 | args.append("-1") 135 | 136 | if let time, time > 0 { 137 | options.append("start=\(Int(time))") 138 | } 139 | 140 | if !options.isEmpty { 141 | args.append(options.joined(separator: ",")) 142 | } 143 | 144 | command("loadfile", args: args) 145 | } 146 | 147 | func play() { 148 | setFlag("pause", false) 149 | } 150 | 151 | func pause() { 152 | setFlag("pause", true) 153 | } 154 | 155 | private func getDouble(_ name: String) -> Double { 156 | guard mpv != nil else { return 0.0 } 157 | var data = Double() 158 | mpv_get_property(mpv, name, MPV_FORMAT_DOUBLE, &data) 159 | return data 160 | } 161 | 162 | private func getString(_ name: String) -> String? { 163 | guard mpv != nil else { return nil } 164 | let cstr = mpv_get_property_string(mpv, name) 165 | let str: String? = cstr == nil ? nil : String(cString: cstr!) 166 | mpv_free(cstr) 167 | return str 168 | } 169 | 170 | func setFlag(_ name: String, _ flag: Bool) { 171 | guard mpv != nil else { return } 172 | var data: Int = flag ? 1 : 0 173 | mpv_set_property(mpv, name, MPV_FORMAT_FLAG, &data) 174 | } 175 | 176 | func command( 177 | _ command: String, 178 | args: [String?] = [], 179 | checkForErrors: Bool = true, 180 | returnValueCallback: ((Int32) -> Void)? = nil 181 | ) { 182 | guard mpv != nil else { 183 | return 184 | } 185 | var cargs = makeCArgs(command, args).map { $0.flatMap { UnsafePointer(strdup($0)) } } 186 | defer { 187 | for ptr in cargs where ptr != nil { 188 | free(UnsafeMutablePointer(mutating: ptr!)) 189 | } 190 | } 191 | //print("\(command) -- \(args)") 192 | let returnValue = mpv_command(mpv, &cargs) 193 | if checkForErrors { 194 | checkError(returnValue) 195 | } 196 | if let cb = returnValueCallback { 197 | cb(returnValue) 198 | } 199 | } 200 | 201 | 202 | 203 | private func makeCArgs(_ command: String, _ args: [String?]) -> [String?] { 204 | if !args.isEmpty, args.last == nil { 205 | fatalError("Command do not need a nil suffix") 206 | } 207 | 208 | var strArgs = args 209 | strArgs.insert(command, at: 0) 210 | strArgs.append(nil) 211 | 212 | return strArgs 213 | } 214 | 215 | func readEvents() { 216 | queue.async { [self] in 217 | while self.mpv != nil { 218 | let event = mpv_wait_event(self.mpv, 0) 219 | if event?.pointee.event_id == MPV_EVENT_NONE { 220 | break 221 | } 222 | 223 | switch event!.pointee.event_id { 224 | case MPV_EVENT_PROPERTY_CHANGE: 225 | let dataOpaquePtr = OpaquePointer(event!.pointee.data) 226 | if let property = UnsafePointer(dataOpaquePtr)?.pointee { 227 | let propertyName = String(cString: property.name) 228 | switch propertyName { 229 | case MPVProperty.videoParamsSigPeak: 230 | if let sigPeak = UnsafePointer(OpaquePointer(property.data))?.pointee { 231 | DispatchQueue.main.async { 232 | self.playDelegate?.propertyChange(mpv: self.mpv, propertyName: propertyName, data: sigPeak) 233 | } 234 | } 235 | case MPVProperty.pausedForCache: 236 | let buffering = UnsafePointer(OpaquePointer(property.data))?.pointee ?? true 237 | DispatchQueue.main.async { 238 | self.playDelegate?.propertyChange(mpv: self.mpv, propertyName: propertyName, data: buffering) 239 | } 240 | default: break 241 | } 242 | } 243 | case MPV_EVENT_SHUTDOWN: 244 | print("event: shutdown\n"); 245 | mpv_terminate_destroy(mpv); 246 | mpv = nil; 247 | break; 248 | case MPV_EVENT_LOG_MESSAGE: 249 | let msg = UnsafeMutablePointer(OpaquePointer(event!.pointee.data)) 250 | print("[\(String(cString: (msg!.pointee.prefix)!))] \(String(cString: (msg!.pointee.level)!)): \(String(cString: (msg!.pointee.text)!))", terminator: "") 251 | default: 252 | let eventName = mpv_event_name(event!.pointee.event_id ) 253 | print("event: \(String(cString: (eventName)!))"); 254 | } 255 | 256 | } 257 | } 258 | } 259 | 260 | 261 | private func checkError(_ status: CInt) { 262 | if status < 0 { 263 | print("MPV API error: \(String(cString: mpv_error_string(status)))\n") 264 | } 265 | } 266 | 267 | } 268 | -------------------------------------------------------------------------------- /Demo/Demo-macOS/Demo-macOS/Player/Metal/MetalLayer.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import AppKit 3 | 4 | // workaround for MoltenVK problem that causes flicker 5 | // https://github.com/mpv-player/mpv/pull/13651 6 | class MetalLayer: CAMetalLayer { 7 | 8 | // workaround for a MoltenVK that sets the drawableSize to 1x1 to forcefully complete 9 | // the presentation, this causes flicker and the drawableSize possibly staying at 1x1 10 | override var drawableSize: CGSize { 11 | get { return super.drawableSize } 12 | set { 13 | if Int(newValue.width) > 1 && Int(newValue.height) > 1 { 14 | super.drawableSize = newValue 15 | } 16 | } 17 | } 18 | 19 | // Hack for fix [target-colorspace-hint] option: 20 | // Update wantsExtendedDynamicRangeContent need run in main thread to activate screen EDR mode, other thread can't activate 21 | override var wantsExtendedDynamicRangeContent: Bool { 22 | get { 23 | return super.wantsExtendedDynamicRangeContent 24 | } 25 | set { 26 | if Thread.isMainThread { 27 | super.wantsExtendedDynamicRangeContent = newValue 28 | } else { 29 | DispatchQueue.main.sync { 30 | super.wantsExtendedDynamicRangeContent = newValue 31 | } 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Demo/Demo-macOS/Demo-macOS/Player/OpenGL/MPVOGLView.swift: -------------------------------------------------------------------------------- 1 | import AppKit 2 | import OpenGL.GL 3 | import OpenGL.GL3 4 | import Libmpv 5 | 6 | final class MPVOGLView: NSOpenGLView { 7 | var mpv: OpaquePointer! 8 | var mpvGL: OpaquePointer! 9 | var playDelegate: MPVPlayerDelegate? 10 | var queue = DispatchQueue(label: "mpv", qos: .userInteractive) 11 | private var defaultFBO: GLint = -1 12 | 13 | override class func defaultPixelFormat() -> NSOpenGLPixelFormat { 14 | let attributes: [NSOpenGLPixelFormatAttribute] = [ 15 | // Must specify the 3.2 Core Profile to use OpenGL 3.2 16 | // NSOpenGLPixelFormatAttribute(NSOpenGLPFAOpenGLProfile), NSOpenGLPixelFormatAttribute(NSOpenGLProfileVersion3_2Core), 17 | 18 | NSOpenGLPixelFormatAttribute(NSOpenGLPFADoubleBuffer), 19 | 20 | NSOpenGLPixelFormatAttribute(NSOpenGLPFAColorSize), NSOpenGLPixelFormatAttribute(32), 21 | NSOpenGLPixelFormatAttribute(NSOpenGLPFADepthSize), NSOpenGLPixelFormatAttribute(24), 22 | NSOpenGLPixelFormatAttribute(NSOpenGLPFAStencilSize), NSOpenGLPixelFormatAttribute(8), 23 | 24 | NSOpenGLPixelFormatAttribute(NSOpenGLPFAMultisample), 25 | NSOpenGLPixelFormatAttribute(NSOpenGLPFASampleBuffers), NSOpenGLPixelFormatAttribute(1), 26 | NSOpenGLPixelFormatAttribute(NSOpenGLPFASamples), NSOpenGLPixelFormatAttribute(4), 27 | NSOpenGLPixelFormatAttribute(0) 28 | ] 29 | 30 | return NSOpenGLPixelFormat(attributes: attributes)! 31 | } 32 | 33 | 34 | func setupContext() { 35 | self.autoresizingMask = [.width, .height] 36 | self.openGLContext!.makeCurrentContext() 37 | } 38 | 39 | func setupMpv() { 40 | mpv = mpv_create() 41 | if mpv == nil { 42 | print("failed creating context\n") 43 | exit(1) 44 | } 45 | 46 | // https://mpv.io/manual/stable/#options 47 | #if DEBUG 48 | checkError(mpv_request_log_messages(mpv, "debug")) 49 | #else 50 | checkError(mpv_request_log_messages(mpv, "no")) 51 | #endif 52 | #if os(macOS) 53 | checkError(mpv_set_option_string(mpv, "input-media-keys", "yes")) 54 | #endif 55 | checkError(mpv_set_option_string(mpv, "subs-match-os-language", "yes")) 56 | checkError(mpv_set_option_string(mpv, "subs-fallback", "yes")) 57 | checkError(mpv_set_option_string(mpv, "hwdec", "auto-safe")) 58 | checkError(mpv_set_option_string(mpv, "vo", "libmpv")) 59 | 60 | checkError(mpv_initialize(mpv)) 61 | 62 | let api = UnsafeMutableRawPointer(mutating: (MPV_RENDER_API_TYPE_OPENGL as NSString).utf8String) 63 | var initParams = mpv_opengl_init_params( 64 | get_proc_address: { 65 | (ctx, name) in 66 | return MPVOGLView.getProcAddress(ctx, name) 67 | }, 68 | get_proc_address_ctx: nil 69 | ) 70 | 71 | withUnsafeMutablePointer(to: &initParams) { initParams in 72 | var params = [ 73 | mpv_render_param(type: MPV_RENDER_PARAM_API_TYPE, data: api), 74 | mpv_render_param(type: MPV_RENDER_PARAM_OPENGL_INIT_PARAMS, data: initParams), 75 | mpv_render_param() 76 | ] 77 | 78 | if mpv_render_context_create(&mpvGL, mpv, ¶ms) < 0 { 79 | puts("failed to initialize mpv GL context") 80 | exit(1) 81 | } 82 | 83 | mpv_render_context_set_update_callback( 84 | mpvGL, 85 | mpvGLUpdate, 86 | UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque()) 87 | ) 88 | 89 | } 90 | 91 | mpv_observe_property(mpv, 0, MPVProperty.pausedForCache, MPV_FORMAT_FLAG) 92 | mpv_set_wakeup_callback(self.mpv, mpvWakeUp, UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque())) 93 | } 94 | 95 | 96 | func loadFile( 97 | _ url: URL 98 | ) { 99 | var args = [url.absoluteString] 100 | var options = [String]() 101 | 102 | args.append("replace") 103 | 104 | if !options.isEmpty { 105 | args.append(options.joined(separator: ",")) 106 | } 107 | 108 | command("loadfile", args: args) 109 | } 110 | 111 | func getDouble(_ name: String) -> Double { 112 | guard mpv != nil else { return 0.0 } 113 | var data = Double() 114 | mpv_get_property(mpv, name, MPV_FORMAT_DOUBLE, &data) 115 | return data 116 | } 117 | 118 | func getString(_ name: String) -> String? { 119 | guard mpv != nil else { return nil } 120 | let cstr = mpv_get_property_string(mpv, name) 121 | let str: String? = cstr == nil ? nil : String(cString: cstr!) 122 | mpv_free(cstr) 123 | return str 124 | } 125 | 126 | func setFlag(_ name: String, _ flag: Bool) { 127 | guard mpv != nil else { return } 128 | var data: Int = flag ? 1 : 0 129 | mpv_set_property(mpv, name, MPV_FORMAT_FLAG, &data) 130 | } 131 | 132 | func command( 133 | _ command: String, 134 | args: [String?] = [], 135 | checkForErrors: Bool = true, 136 | returnValueCallback: ((Int32) -> Void)? = nil 137 | ) { 138 | guard mpv != nil else { 139 | return 140 | } 141 | var cargs = makeCArgs(command, args).map { $0.flatMap { UnsafePointer(strdup($0)) } } 142 | defer { 143 | for ptr in cargs where ptr != nil { 144 | free(UnsafeMutablePointer(mutating: ptr!)) 145 | } 146 | } 147 | //print("\(command) -- \(args)") 148 | let returnValue = mpv_command(mpv, &cargs) 149 | if checkForErrors { 150 | checkError(returnValue) 151 | } 152 | if let cb = returnValueCallback { 153 | cb(returnValue) 154 | } 155 | } 156 | 157 | private func makeCArgs(_ command: String, _ args: [String?]) -> [String?] { 158 | if !args.isEmpty, args.last == nil { 159 | fatalError("Command do not need a nil suffix") 160 | } 161 | 162 | var strArgs = args 163 | strArgs.insert(command, at: 0) 164 | strArgs.append(nil) 165 | 166 | return strArgs 167 | } 168 | 169 | 170 | func readEvents() { 171 | queue.async { [self] in 172 | while self.mpv != nil { 173 | let event = mpv_wait_event(self.mpv, 0) 174 | if event!.pointee.event_id == MPV_EVENT_NONE { 175 | break 176 | } 177 | switch event!.pointee.event_id { 178 | case MPV_EVENT_PROPERTY_CHANGE: 179 | let dataOpaquePtr = OpaquePointer(event!.pointee.data) 180 | if let property = UnsafePointer(dataOpaquePtr)?.pointee { 181 | let propertyName = String(cString: property.name) 182 | switch propertyName { 183 | case MPVProperty.videoParamsSigPeak: 184 | if let sigPeak = UnsafePointer(OpaquePointer(property.data))?.pointee { 185 | DispatchQueue.main.async { 186 | self.playDelegate?.propertyChange(mpv: self.mpv, propertyName: propertyName, data: sigPeak) 187 | } 188 | } 189 | case MPVProperty.pausedForCache: 190 | let buffering = UnsafePointer(OpaquePointer(property.data))?.pointee ?? true 191 | DispatchQueue.main.async { 192 | self.playDelegate?.propertyChange(mpv: self.mpv, propertyName: propertyName, data: buffering) 193 | } 194 | default: break 195 | } 196 | } 197 | case MPV_EVENT_SHUTDOWN: 198 | mpv_render_context_free(mpvGL); 199 | mpv_terminate_destroy(mpv); 200 | mpv = nil; 201 | print("event: shutdown\n"); 202 | break; 203 | case MPV_EVENT_LOG_MESSAGE: 204 | let msg = UnsafeMutablePointer(OpaquePointer(event!.pointee.data)) 205 | print("[\(String(cString: (msg!.pointee.prefix)!))] \(String(cString: (msg!.pointee.level)!)): \(String(cString: (msg!.pointee.text)!))", terminator: "") 206 | default: 207 | let eventName = mpv_event_name(event!.pointee.event_id ) 208 | print("event: \(String(cString: (eventName)!))"); 209 | } 210 | } 211 | } 212 | } 213 | 214 | 215 | private func checkError(_ status: CInt) { 216 | if status < 0 { 217 | print("MPV API error: \(String(cString: mpv_error_string(status)))\n") 218 | } 219 | } 220 | 221 | 222 | private var machine: String { 223 | var systeminfo = utsname() 224 | uname(&systeminfo) 225 | return withUnsafeBytes(of: &systeminfo.machine) { bufPtr -> String in 226 | let data = Data(bufPtr) 227 | if let lastIndex = data.lastIndex(where: { $0 != 0 }) { 228 | return String(data: data[0 ... lastIndex], encoding: .isoLatin1)! 229 | } else { 230 | return String(data: data, encoding: .isoLatin1)! 231 | } 232 | } 233 | } 234 | 235 | 236 | 237 | override func draw(_ dirtyRect: NSRect) { 238 | guard let mpvGL else { 239 | return 240 | } 241 | 242 | // fill black background 243 | glClearColor(0, 0, 0, 0) 244 | glClear(UInt32(GL_COLOR_BUFFER_BIT)) 245 | 246 | glGetIntegerv(UInt32(GL_FRAMEBUFFER_BINDING), &defaultFBO) 247 | 248 | var dims: [GLint] = [0, 0, 0, 0] 249 | glGetIntegerv(GLenum(GL_VIEWPORT), &dims) 250 | 251 | var data = mpv_opengl_fbo( 252 | fbo: Int32(defaultFBO), 253 | w: Int32(dims[2]), 254 | h: Int32(dims[3]), 255 | internal_format: 0 256 | ) 257 | var flip: CInt = 1 258 | withUnsafeMutablePointer(to: &flip) { flip in 259 | withUnsafeMutablePointer(to: &data) { data in 260 | var params = [ 261 | mpv_render_param(type: MPV_RENDER_PARAM_OPENGL_FBO, data: data), 262 | mpv_render_param(type: MPV_RENDER_PARAM_FLIP_Y, data: flip), 263 | mpv_render_param() 264 | ] 265 | mpv_render_context_render(mpvGL, ¶ms) 266 | } 267 | } 268 | 269 | self.openGLContext!.flushBuffer() 270 | } 271 | 272 | 273 | private static func getProcAddress(_: UnsafeMutableRawPointer?, _ name: UnsafePointer?) -> UnsafeMutableRawPointer? { 274 | let symbolName = CFStringCreateWithCString(kCFAllocatorDefault, name, CFStringBuiltInEncodings.ASCII.rawValue) 275 | let identifier = CFBundleGetBundleWithIdentifier("com.apple.opengl" as CFString) 276 | 277 | return CFBundleGetFunctionPointerForName(identifier, symbolName) 278 | } 279 | 280 | } 281 | 282 | 283 | 284 | func mpvGLUpdate(_ ctx: UnsafeMutableRawPointer?) { 285 | let glView = unsafeBitCast(ctx, to: MPVOGLView.self) 286 | 287 | DispatchQueue.main.async { 288 | glView.display() 289 | } 290 | } 291 | 292 | 293 | func mpvWakeUp(_ ctx: UnsafeMutableRawPointer?) { 294 | let glView = unsafeBitCast(ctx, to: MPVOGLView.self) 295 | glView.readEvents() 296 | } 297 | -------------------------------------------------------------------------------- /Demo/Demo-macOS/Demo-macOS/Player/OpenGL/MPVPlayerView.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import SwiftUI 3 | 4 | struct MPVPlayerView: NSViewControllerRepresentable { 5 | @ObservedObject var coordinator: Coordinator 6 | 7 | func makeNSViewController(context: Context) -> some NSViewController { 8 | let mpv = MPVViewController() 9 | mpv.playDelegate = coordinator 10 | mpv.playUrl = coordinator.playUrl 11 | 12 | context.coordinator.player = mpv 13 | return mpv 14 | } 15 | 16 | func updateNSViewController(_ nsViewController: NSViewControllerType, context: Context) { 17 | 18 | } 19 | 20 | public func makeCoordinator() -> Coordinator { 21 | coordinator 22 | } 23 | 24 | func play(_ url: URL) -> Self { 25 | coordinator.playUrl = url 26 | return self 27 | } 28 | 29 | func onPropertyChange(_ handler: @escaping (MPVViewController, String, Any?) -> Void) -> Self { 30 | coordinator.onPropertyChange = handler 31 | return self 32 | } 33 | 34 | @MainActor 35 | public final class Coordinator: MPVPlayerDelegate, ObservableObject { 36 | weak var player: MPVViewController? 37 | 38 | var playUrl : URL? 39 | var onPropertyChange: ((MPVViewController, String, Any?) -> Void)? 40 | 41 | @Published var pause : Bool = false { 42 | didSet { 43 | if pause { 44 | self.player?.pause() 45 | } else { 46 | self.player?.play() 47 | } 48 | } 49 | } 50 | 51 | @Published var hdrEnabled : Bool = false { 52 | didSet { 53 | self.player?.hdrEnabled = hdrEnabled 54 | } 55 | } 56 | 57 | @Published var hdrAvailable : Bool = false 58 | @Published var edrRange : String = "1.0" 59 | 60 | 61 | func play(_ url: URL) { 62 | player?.loadFile(url) 63 | } 64 | 65 | func propertyChange(mpv: OpaquePointer, propertyName: String, data: Any?) { 66 | guard let player else { return } 67 | 68 | self.onPropertyChange?(player, propertyName, data) 69 | } 70 | } 71 | } 72 | 73 | -------------------------------------------------------------------------------- /Demo/Demo-macOS/Demo-macOS/Player/OpenGL/MPVViewController.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import AppKit 3 | 4 | class MPVViewController: NSViewController { 5 | var glView : MPVOGLView! 6 | var playDelegate: MPVPlayerDelegate? 7 | var playUrl: URL? 8 | var hdrAvailable : Bool { 9 | let maxEDRRange = NSScreen.main?.maximumPotentialExtendedDynamicRangeColorComponentValue ?? 1.0 10 | let sigPeak = glView.getDouble(MPVProperty.videoParamsSigPeak) 11 | // display screen support HDR and current playing HDR video 12 | return maxEDRRange > 1.0 && sigPeak > 1.0 13 | } 14 | var hdrEnabled = false 15 | 16 | override func loadView() { 17 | self.view = NSView(frame: .init(x: 0, y: 0, width: NSScreen.main!.frame.width, height: NSScreen.main!.frame.height)) 18 | self.glView = MPVOGLView(frame: self.view.bounds) 19 | self.glView.wantsLayer = true 20 | self.glView.playDelegate = self.playDelegate 21 | 22 | self.view.addSubview(glView) 23 | } 24 | 25 | override func viewDidLoad() { 26 | super.viewDidLoad() 27 | 28 | self.glView.setupContext() 29 | self.glView.setupMpv() 30 | 31 | if let url = playUrl { 32 | self.glView.loadFile(url) 33 | } 34 | } 35 | 36 | func loadFile(_ url: URL) { 37 | self.glView.loadFile(url) 38 | } 39 | 40 | func play() { 41 | self.glView.setFlag("pause", false) 42 | } 43 | 44 | func pause() { 45 | self.glView.setFlag("pause", true) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Demo/Demo-macOS/Demo-macOS/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Demo/Demo-tvOS/Demo-tvOS.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 56; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | F4850CAA2BEE59C900ABB79A /* MPVProperty.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4850CA82BEE59C900ABB79A /* MPVProperty.swift */; }; 11 | F4850CAB2BEE59C900ABB79A /* MPVPlayerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4850CA92BEE59C900ABB79A /* MPVPlayerDelegate.swift */; }; 12 | F4D866A02CBCAFF6009CC9F8 /* MPVKit-GPL in Frameworks */ = {isa = PBXBuildFile; productRef = F4D8669F2CBCAFF6009CC9F8 /* MPVKit-GPL */; }; 13 | F4D8D7962BD7553E00246C75 /* MPVMetalPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4D8D7952BD7553E00246C75 /* MPVMetalPlayerView.swift */; }; 14 | F4D8D7982BD7559700246C75 /* MetalLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4D8D7972BD7559700246C75 /* MetalLayer.swift */; }; 15 | F4EB5D162B61E45000486E3F /* Demo_tvOSApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4EB5D152B61E45000486E3F /* Demo_tvOSApp.swift */; }; 16 | F4EB5D182B61E45000486E3F /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4EB5D172B61E45000486E3F /* ContentView.swift */; }; 17 | F4EB5D1A2B61E45200486E3F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F4EB5D192B61E45200486E3F /* Assets.xcassets */; }; 18 | F4EB5D1D2B61E45200486E3F /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F4EB5D1C2B61E45200486E3F /* Preview Assets.xcassets */; }; 19 | F4EB5D292B61E48A00486E3F /* MPVMetalViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4EB5D242B61E48A00486E3F /* MPVMetalViewController.swift */; }; 20 | F4EB5D2A2B61E48A00486E3F /* MPVViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4EB5D252B61E48A00486E3F /* MPVViewController.swift */; }; 21 | F4EB5D2C2B61E48A00486E3F /* MPVPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4EB5D272B61E48A00486E3F /* MPVPlayerView.swift */; }; 22 | /* End PBXBuildFile section */ 23 | 24 | /* Begin PBXFileReference section */ 25 | F4850CA82BEE59C900ABB79A /* MPVProperty.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MPVProperty.swift; sourceTree = ""; }; 26 | F4850CA92BEE59C900ABB79A /* MPVPlayerDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MPVPlayerDelegate.swift; sourceTree = ""; }; 27 | F4D8669E2CBCAFD1009CC9F8 /* MPVKit */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = MPVKit; path = ../..; sourceTree = ""; }; 28 | F4D8D7952BD7553E00246C75 /* MPVMetalPlayerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MPVMetalPlayerView.swift; sourceTree = ""; }; 29 | F4D8D7972BD7559700246C75 /* MetalLayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetalLayer.swift; sourceTree = ""; }; 30 | F4EB5D122B61E45000486E3F /* Demo-tvOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Demo-tvOS.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 31 | F4EB5D152B61E45000486E3F /* Demo_tvOSApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Demo_tvOSApp.swift; sourceTree = ""; }; 32 | F4EB5D172B61E45000486E3F /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 33 | F4EB5D192B61E45200486E3F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 34 | F4EB5D1C2B61E45200486E3F /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 35 | F4EB5D242B61E48A00486E3F /* MPVMetalViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MPVMetalViewController.swift; sourceTree = ""; }; 36 | F4EB5D252B61E48A00486E3F /* MPVViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MPVViewController.swift; sourceTree = ""; }; 37 | F4EB5D272B61E48A00486E3F /* MPVPlayerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MPVPlayerView.swift; sourceTree = ""; }; 38 | /* End PBXFileReference section */ 39 | 40 | /* Begin PBXFrameworksBuildPhase section */ 41 | F4EB5D0F2B61E45000486E3F /* Frameworks */ = { 42 | isa = PBXFrameworksBuildPhase; 43 | buildActionMask = 2147483647; 44 | files = ( 45 | F4D866A02CBCAFF6009CC9F8 /* MPVKit-GPL in Frameworks */, 46 | ); 47 | runOnlyForDeploymentPostprocessing = 0; 48 | }; 49 | /* End PBXFrameworksBuildPhase section */ 50 | 51 | /* Begin PBXGroup section */ 52 | F4BA11AB2BCCFD9C00B472DE /* OpenGL */ = { 53 | isa = PBXGroup; 54 | children = ( 55 | F4EB5D272B61E48A00486E3F /* MPVPlayerView.swift */, 56 | F4EB5D252B61E48A00486E3F /* MPVViewController.swift */, 57 | ); 58 | path = OpenGL; 59 | sourceTree = ""; 60 | }; 61 | F4BA11AC2BCCFE3A00B472DE /* Metal */ = { 62 | isa = PBXGroup; 63 | children = ( 64 | F4D8D7952BD7553E00246C75 /* MPVMetalPlayerView.swift */, 65 | F4EB5D242B61E48A00486E3F /* MPVMetalViewController.swift */, 66 | F4D8D7972BD7559700246C75 /* MetalLayer.swift */, 67 | ); 68 | path = Metal; 69 | sourceTree = ""; 70 | }; 71 | F4D8669D2CBCAFD1009CC9F8 /* Frameworks */ = { 72 | isa = PBXGroup; 73 | children = ( 74 | F4D8669E2CBCAFD1009CC9F8 /* MPVKit */, 75 | ); 76 | name = Frameworks; 77 | sourceTree = ""; 78 | }; 79 | F4EB5D092B61E45000486E3F = { 80 | isa = PBXGroup; 81 | children = ( 82 | F4EB5D142B61E45000486E3F /* Demo-tvOS */, 83 | F4D8669D2CBCAFD1009CC9F8 /* Frameworks */, 84 | F4EB5D132B61E45000486E3F /* Products */, 85 | ); 86 | sourceTree = ""; 87 | }; 88 | F4EB5D132B61E45000486E3F /* Products */ = { 89 | isa = PBXGroup; 90 | children = ( 91 | F4EB5D122B61E45000486E3F /* Demo-tvOS.app */, 92 | ); 93 | name = Products; 94 | sourceTree = ""; 95 | }; 96 | F4EB5D142B61E45000486E3F /* Demo-tvOS */ = { 97 | isa = PBXGroup; 98 | children = ( 99 | F4EB5D232B61E48A00486E3F /* Player */, 100 | F4EB5D152B61E45000486E3F /* Demo_tvOSApp.swift */, 101 | F4EB5D172B61E45000486E3F /* ContentView.swift */, 102 | F4EB5D192B61E45200486E3F /* Assets.xcassets */, 103 | F4EB5D1B2B61E45200486E3F /* Preview Content */, 104 | ); 105 | path = "Demo-tvOS"; 106 | sourceTree = ""; 107 | }; 108 | F4EB5D1B2B61E45200486E3F /* Preview Content */ = { 109 | isa = PBXGroup; 110 | children = ( 111 | F4EB5D1C2B61E45200486E3F /* Preview Assets.xcassets */, 112 | ); 113 | path = "Preview Content"; 114 | sourceTree = ""; 115 | }; 116 | F4EB5D232B61E48A00486E3F /* Player */ = { 117 | isa = PBXGroup; 118 | children = ( 119 | F4850CA92BEE59C900ABB79A /* MPVPlayerDelegate.swift */, 120 | F4850CA82BEE59C900ABB79A /* MPVProperty.swift */, 121 | F4BA11AB2BCCFD9C00B472DE /* OpenGL */, 122 | F4BA11AC2BCCFE3A00B472DE /* Metal */, 123 | ); 124 | path = Player; 125 | sourceTree = ""; 126 | }; 127 | /* End PBXGroup section */ 128 | 129 | /* Begin PBXNativeTarget section */ 130 | F4EB5D112B61E45000486E3F /* Demo-tvOS */ = { 131 | isa = PBXNativeTarget; 132 | buildConfigurationList = F4EB5D202B61E45200486E3F /* Build configuration list for PBXNativeTarget "Demo-tvOS" */; 133 | buildPhases = ( 134 | F4EB5D0E2B61E45000486E3F /* Sources */, 135 | F4EB5D0F2B61E45000486E3F /* Frameworks */, 136 | F4EB5D102B61E45000486E3F /* Resources */, 137 | ); 138 | buildRules = ( 139 | ); 140 | dependencies = ( 141 | ); 142 | name = "Demo-tvOS"; 143 | packageProductDependencies = ( 144 | F4D8669F2CBCAFF6009CC9F8 /* MPVKit-GPL */, 145 | ); 146 | productName = "Demo-tvOS"; 147 | productReference = F4EB5D122B61E45000486E3F /* Demo-tvOS.app */; 148 | productType = "com.apple.product-type.application"; 149 | }; 150 | /* End PBXNativeTarget section */ 151 | 152 | /* Begin PBXProject section */ 153 | F4EB5D0A2B61E45000486E3F /* Project object */ = { 154 | isa = PBXProject; 155 | attributes = { 156 | BuildIndependentTargetsInParallel = 1; 157 | LastSwiftUpdateCheck = 1430; 158 | LastUpgradeCheck = 1600; 159 | TargetAttributes = { 160 | F4EB5D112B61E45000486E3F = { 161 | CreatedOnToolsVersion = 14.3.1; 162 | }; 163 | }; 164 | }; 165 | buildConfigurationList = F4EB5D0D2B61E45000486E3F /* Build configuration list for PBXProject "Demo-tvOS" */; 166 | compatibilityVersion = "Xcode 14.0"; 167 | developmentRegion = en; 168 | hasScannedForEncodings = 0; 169 | knownRegions = ( 170 | en, 171 | Base, 172 | ); 173 | mainGroup = F4EB5D092B61E45000486E3F; 174 | packageReferences = ( 175 | ); 176 | productRefGroup = F4EB5D132B61E45000486E3F /* Products */; 177 | projectDirPath = ""; 178 | projectRoot = ""; 179 | targets = ( 180 | F4EB5D112B61E45000486E3F /* Demo-tvOS */, 181 | ); 182 | }; 183 | /* End PBXProject section */ 184 | 185 | /* Begin PBXResourcesBuildPhase section */ 186 | F4EB5D102B61E45000486E3F /* Resources */ = { 187 | isa = PBXResourcesBuildPhase; 188 | buildActionMask = 2147483647; 189 | files = ( 190 | F4EB5D1D2B61E45200486E3F /* Preview Assets.xcassets in Resources */, 191 | F4EB5D1A2B61E45200486E3F /* Assets.xcassets in Resources */, 192 | ); 193 | runOnlyForDeploymentPostprocessing = 0; 194 | }; 195 | /* End PBXResourcesBuildPhase section */ 196 | 197 | /* Begin PBXSourcesBuildPhase section */ 198 | F4EB5D0E2B61E45000486E3F /* Sources */ = { 199 | isa = PBXSourcesBuildPhase; 200 | buildActionMask = 2147483647; 201 | files = ( 202 | F4EB5D2A2B61E48A00486E3F /* MPVViewController.swift in Sources */, 203 | F4D8D7962BD7553E00246C75 /* MPVMetalPlayerView.swift in Sources */, 204 | F4EB5D292B61E48A00486E3F /* MPVMetalViewController.swift in Sources */, 205 | F4D8D7982BD7559700246C75 /* MetalLayer.swift in Sources */, 206 | F4EB5D182B61E45000486E3F /* ContentView.swift in Sources */, 207 | F4850CAA2BEE59C900ABB79A /* MPVProperty.swift in Sources */, 208 | F4EB5D162B61E45000486E3F /* Demo_tvOSApp.swift in Sources */, 209 | F4EB5D2C2B61E48A00486E3F /* MPVPlayerView.swift in Sources */, 210 | F4850CAB2BEE59C900ABB79A /* MPVPlayerDelegate.swift in Sources */, 211 | ); 212 | runOnlyForDeploymentPostprocessing = 0; 213 | }; 214 | /* End PBXSourcesBuildPhase section */ 215 | 216 | /* Begin XCBuildConfiguration section */ 217 | F4EB5D1E2B61E45200486E3F /* Debug */ = { 218 | isa = XCBuildConfiguration; 219 | buildSettings = { 220 | ALWAYS_SEARCH_USER_PATHS = NO; 221 | CLANG_ANALYZER_NONNULL = YES; 222 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 223 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 224 | CLANG_ENABLE_MODULES = YES; 225 | CLANG_ENABLE_OBJC_ARC = YES; 226 | CLANG_ENABLE_OBJC_WEAK = YES; 227 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 228 | CLANG_WARN_BOOL_CONVERSION = YES; 229 | CLANG_WARN_COMMA = YES; 230 | CLANG_WARN_CONSTANT_CONVERSION = YES; 231 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 232 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 233 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 234 | CLANG_WARN_EMPTY_BODY = YES; 235 | CLANG_WARN_ENUM_CONVERSION = YES; 236 | CLANG_WARN_INFINITE_RECURSION = YES; 237 | CLANG_WARN_INT_CONVERSION = YES; 238 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 239 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 240 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 241 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 242 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 243 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 244 | CLANG_WARN_STRICT_PROTOTYPES = YES; 245 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 246 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 247 | CLANG_WARN_UNREACHABLE_CODE = YES; 248 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 249 | COPY_PHASE_STRIP = NO; 250 | DEBUG_INFORMATION_FORMAT = dwarf; 251 | ENABLE_STRICT_OBJC_MSGSEND = YES; 252 | ENABLE_TESTABILITY = YES; 253 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 254 | GCC_C_LANGUAGE_STANDARD = gnu11; 255 | GCC_DYNAMIC_NO_PIC = NO; 256 | GCC_NO_COMMON_BLOCKS = YES; 257 | GCC_OPTIMIZATION_LEVEL = 0; 258 | GCC_PREPROCESSOR_DEFINITIONS = ( 259 | "DEBUG=1", 260 | "$(inherited)", 261 | ); 262 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 263 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 264 | GCC_WARN_UNDECLARED_SELECTOR = YES; 265 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 266 | GCC_WARN_UNUSED_FUNCTION = YES; 267 | GCC_WARN_UNUSED_VARIABLE = YES; 268 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 269 | MTL_FAST_MATH = YES; 270 | ONLY_ACTIVE_ARCH = YES; 271 | SDKROOT = appletvos; 272 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 273 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 274 | TVOS_DEPLOYMENT_TARGET = 16.4; 275 | }; 276 | name = Debug; 277 | }; 278 | F4EB5D1F2B61E45200486E3F /* Release */ = { 279 | isa = XCBuildConfiguration; 280 | buildSettings = { 281 | ALWAYS_SEARCH_USER_PATHS = NO; 282 | CLANG_ANALYZER_NONNULL = YES; 283 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 284 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 285 | CLANG_ENABLE_MODULES = YES; 286 | CLANG_ENABLE_OBJC_ARC = YES; 287 | CLANG_ENABLE_OBJC_WEAK = YES; 288 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 289 | CLANG_WARN_BOOL_CONVERSION = YES; 290 | CLANG_WARN_COMMA = YES; 291 | CLANG_WARN_CONSTANT_CONVERSION = YES; 292 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 293 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 294 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 295 | CLANG_WARN_EMPTY_BODY = YES; 296 | CLANG_WARN_ENUM_CONVERSION = YES; 297 | CLANG_WARN_INFINITE_RECURSION = YES; 298 | CLANG_WARN_INT_CONVERSION = YES; 299 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 300 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 301 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 302 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 303 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 304 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 305 | CLANG_WARN_STRICT_PROTOTYPES = YES; 306 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 307 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 308 | CLANG_WARN_UNREACHABLE_CODE = YES; 309 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 310 | COPY_PHASE_STRIP = NO; 311 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 312 | ENABLE_NS_ASSERTIONS = NO; 313 | ENABLE_STRICT_OBJC_MSGSEND = YES; 314 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 315 | GCC_C_LANGUAGE_STANDARD = gnu11; 316 | GCC_NO_COMMON_BLOCKS = YES; 317 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 318 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 319 | GCC_WARN_UNDECLARED_SELECTOR = YES; 320 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 321 | GCC_WARN_UNUSED_FUNCTION = YES; 322 | GCC_WARN_UNUSED_VARIABLE = YES; 323 | MTL_ENABLE_DEBUG_INFO = NO; 324 | MTL_FAST_MATH = YES; 325 | SDKROOT = appletvos; 326 | SWIFT_COMPILATION_MODE = wholemodule; 327 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 328 | TVOS_DEPLOYMENT_TARGET = 16.4; 329 | VALIDATE_PRODUCT = YES; 330 | }; 331 | name = Release; 332 | }; 333 | F4EB5D212B61E45200486E3F /* Debug */ = { 334 | isa = XCBuildConfiguration; 335 | buildSettings = { 336 | ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image"; 337 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 338 | CODE_SIGN_STYLE = Automatic; 339 | CURRENT_PROJECT_VERSION = 1; 340 | DEVELOPMENT_ASSET_PATHS = "\"Demo-tvOS/Preview Content\""; 341 | DEVELOPMENT_TEAM = ""; 342 | ENABLE_PREVIEWS = YES; 343 | GENERATE_INFOPLIST_FILE = YES; 344 | INFOPLIST_KEY_UILaunchScreen_Generation = YES; 345 | INFOPLIST_KEY_UIUserInterfaceStyle = Automatic; 346 | LD_RUNPATH_SEARCH_PATHS = ( 347 | "$(inherited)", 348 | "@executable_path/Frameworks", 349 | ); 350 | MARKETING_VERSION = 1.0; 351 | PRODUCT_BUNDLE_IDENTIFIER = "com.mpvkit.Demo-tvOS"; 352 | PRODUCT_NAME = "$(TARGET_NAME)"; 353 | SWIFT_EMIT_LOC_STRINGS = YES; 354 | SWIFT_VERSION = 5.0; 355 | TARGETED_DEVICE_FAMILY = 3; 356 | }; 357 | name = Debug; 358 | }; 359 | F4EB5D222B61E45200486E3F /* Release */ = { 360 | isa = XCBuildConfiguration; 361 | buildSettings = { 362 | ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image"; 363 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 364 | CODE_SIGN_STYLE = Automatic; 365 | CURRENT_PROJECT_VERSION = 1; 366 | DEVELOPMENT_ASSET_PATHS = "\"Demo-tvOS/Preview Content\""; 367 | DEVELOPMENT_TEAM = ""; 368 | ENABLE_PREVIEWS = YES; 369 | GENERATE_INFOPLIST_FILE = YES; 370 | INFOPLIST_KEY_UILaunchScreen_Generation = YES; 371 | INFOPLIST_KEY_UIUserInterfaceStyle = Automatic; 372 | LD_RUNPATH_SEARCH_PATHS = ( 373 | "$(inherited)", 374 | "@executable_path/Frameworks", 375 | ); 376 | MARKETING_VERSION = 1.0; 377 | PRODUCT_BUNDLE_IDENTIFIER = "com.mpvkit.Demo-tvOS"; 378 | PRODUCT_NAME = "$(TARGET_NAME)"; 379 | SWIFT_EMIT_LOC_STRINGS = YES; 380 | SWIFT_VERSION = 5.0; 381 | TARGETED_DEVICE_FAMILY = 3; 382 | }; 383 | name = Release; 384 | }; 385 | /* End XCBuildConfiguration section */ 386 | 387 | /* Begin XCConfigurationList section */ 388 | F4EB5D0D2B61E45000486E3F /* Build configuration list for PBXProject "Demo-tvOS" */ = { 389 | isa = XCConfigurationList; 390 | buildConfigurations = ( 391 | F4EB5D1E2B61E45200486E3F /* Debug */, 392 | F4EB5D1F2B61E45200486E3F /* Release */, 393 | ); 394 | defaultConfigurationIsVisible = 0; 395 | defaultConfigurationName = Release; 396 | }; 397 | F4EB5D202B61E45200486E3F /* Build configuration list for PBXNativeTarget "Demo-tvOS" */ = { 398 | isa = XCConfigurationList; 399 | buildConfigurations = ( 400 | F4EB5D212B61E45200486E3F /* Debug */, 401 | F4EB5D222B61E45200486E3F /* Release */, 402 | ); 403 | defaultConfigurationIsVisible = 0; 404 | defaultConfigurationName = Release; 405 | }; 406 | /* End XCConfigurationList section */ 407 | 408 | /* Begin XCSwiftPackageProductDependency section */ 409 | F4D8669F2CBCAFF6009CC9F8 /* MPVKit-GPL */ = { 410 | isa = XCSwiftPackageProductDependency; 411 | productName = "MPVKit-GPL"; 412 | }; 413 | /* End XCSwiftPackageProductDependency section */ 414 | }; 415 | rootObject = F4EB5D0A2B61E45000486E3F /* Project object */; 416 | } 417 | -------------------------------------------------------------------------------- /Demo/Demo-tvOS/Demo-tvOS.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Demo/Demo-tvOS/Demo-tvOS.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Demo/Demo-tvOS/Demo-tvOS.xcodeproj/xcshareddata/xcschemes/Demo-tvOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 43 | 45 | 51 | 52 | 53 | 54 | 60 | 62 | 68 | 69 | 70 | 71 | 73 | 74 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /Demo/Demo-tvOS/Demo-tvOS/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Demo/Demo-tvOS/Demo-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Demo/Demo-tvOS/Demo-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Demo/Demo-tvOS/Demo-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | }, 6 | "layers" : [ 7 | { 8 | "filename" : "Front.imagestacklayer" 9 | }, 10 | { 11 | "filename" : "Middle.imagestacklayer" 12 | }, 13 | { 14 | "filename" : "Back.imagestacklayer" 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /Demo/Demo-tvOS/Demo-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Demo/Demo-tvOS/Demo-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Demo/Demo-tvOS/Demo-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Demo/Demo-tvOS/Demo-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Demo/Demo-tvOS/Demo-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "tv", 9 | "scale" : "2x" 10 | } 11 | ], 12 | "info" : { 13 | "author" : "xcode", 14 | "version" : 1 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Demo/Demo-tvOS/Demo-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Demo/Demo-tvOS/Demo-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | }, 6 | "layers" : [ 7 | { 8 | "filename" : "Front.imagestacklayer" 9 | }, 10 | { 11 | "filename" : "Middle.imagestacklayer" 12 | }, 13 | { 14 | "filename" : "Back.imagestacklayer" 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /Demo/Demo-tvOS/Demo-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "tv", 9 | "scale" : "2x" 10 | } 11 | ], 12 | "info" : { 13 | "author" : "xcode", 14 | "version" : 1 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Demo/Demo-tvOS/Demo-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Demo/Demo-tvOS/Demo-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "tv", 9 | "scale" : "2x" 10 | } 11 | ], 12 | "info" : { 13 | "author" : "xcode", 14 | "version" : 1 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Demo/Demo-tvOS/Demo-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Demo/Demo-tvOS/Demo-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "assets" : [ 3 | { 4 | "filename" : "App Icon - App Store.imagestack", 5 | "idiom" : "tv", 6 | "role" : "primary-app-icon", 7 | "size" : "1280x768" 8 | }, 9 | { 10 | "filename" : "App Icon.imagestack", 11 | "idiom" : "tv", 12 | "role" : "primary-app-icon", 13 | "size" : "400x240" 14 | }, 15 | { 16 | "filename" : "Top Shelf Image Wide.imageset", 17 | "idiom" : "tv", 18 | "role" : "top-shelf-image-wide", 19 | "size" : "2320x720" 20 | }, 21 | { 22 | "filename" : "Top Shelf Image.imageset", 23 | "idiom" : "tv", 24 | "role" : "top-shelf-image", 25 | "size" : "1920x720" 26 | } 27 | ], 28 | "info" : { 29 | "author" : "xcode", 30 | "version" : 1 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Demo/Demo-tvOS/Demo-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "tv", 9 | "scale" : "2x" 10 | } 11 | ], 12 | "info" : { 13 | "author" : "xcode", 14 | "version" : 1 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Demo/Demo-tvOS/Demo-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "tv", 9 | "scale" : "2x" 10 | } 11 | ], 12 | "info" : { 13 | "author" : "xcode", 14 | "version" : 1 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Demo/Demo-tvOS/Demo-tvOS/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Demo/Demo-tvOS/Demo-tvOS/ContentView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct ContentView: View { 4 | @ObservedObject var coordinator = MPVMetalPlayerView.Coordinator() 5 | @State var loading = false 6 | 7 | var body: some View { 8 | VStack { 9 | MPVMetalPlayerView(coordinator: coordinator) 10 | .play(URL(string: "https://github.com/mpvkit/video-test/raw/master/resources/HDR10_ToneMapping_Test_240_1000_nits.mp4")!) 11 | .onPropertyChange{ player, propertyName, propertyData in 12 | switch propertyName { 13 | case MPVProperty.pausedForCache: 14 | loading = propertyData as! Bool 15 | default: break 16 | } 17 | } 18 | } 19 | .overlay { 20 | VStack { 21 | Spacer() 22 | HStack(alignment: .center) { 23 | Button { 24 | coordinator.play(URL(string: "https://vjs.zencdn.net/v/oceans.mp4")!) 25 | } label: { 26 | Text("h264").frame(width: 100, height: 100) 27 | } 28 | Button { 29 | coordinator.play(URL(string: "https://github.com/mpvkit/video-test/raw/master/resources/h265.mp4")!) 30 | } label: { 31 | Text("h265").frame(width: 130, height: 100) 32 | } 33 | Button { 34 | coordinator.play(URL(string: "https://github.com/mpvkit/video-test/raw/master/resources/pgs_subtitle.mkv")!) 35 | } label: { 36 | Text("subtitle").frame(width: 130, height: 100) 37 | } 38 | Button { 39 | coordinator.play(URL(string: "https://github.com/mpvkit/video-test/raw/master/resources/hdr.mkv")!) 40 | } label: { 41 | Text("HDR").frame(width: 130, height: 100) 42 | } 43 | Button { 44 | coordinator.play(URL(string: "https://github.com/mpvkit/video-test/raw/master/resources/DolbyVision_P5.mp4")!) 45 | } label: { 46 | Text("DV_P5").frame(width: 130, height: 100) 47 | } 48 | Button { 49 | coordinator.play(URL(string: "https://github.com/mpvkit/video-test/raw/master/resources/DolbyVision_P8.mp4")!) 50 | } label: { 51 | Text("DV_P8").frame(width: 130, height: 100) 52 | } 53 | } 54 | .padding(.bottom, 20) 55 | } 56 | } 57 | .overlay(overlayView) 58 | .onPlayPauseCommand { 59 | coordinator.player?.togglePause() 60 | } 61 | .preferredColorScheme(.dark) 62 | } 63 | 64 | @ViewBuilder 65 | private var overlayView: some View { 66 | if loading { 67 | ProgressView() 68 | } else { 69 | EmptyView() 70 | } 71 | } 72 | } 73 | 74 | -------------------------------------------------------------------------------- /Demo/Demo-tvOS/Demo-tvOS/Demo_tvOSApp.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | @main 4 | struct Demo_tvOSApp: App { 5 | var body: some Scene { 6 | WindowGroup { 7 | ContentView() 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Demo/Demo-tvOS/Demo-tvOS/Player/MPVPlayerDelegate.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | @MainActor 4 | public protocol MPVPlayerDelegate: AnyObject { 5 | func propertyChange(mpv: OpaquePointer, propertyName: String, data: Any?) 6 | } 7 | -------------------------------------------------------------------------------- /Demo/Demo-tvOS/Demo-tvOS/Player/MPVProperty.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | struct MPVProperty { 4 | static let videoParamsColormatrix = "video-params/colormatrix" 5 | static let videoParamsColorlevels = "video-params/colorlevels" 6 | static let videoParamsPrimaries = "video-params/primaries" 7 | static let videoParamsGamma = "video-params/gamma" 8 | static let videoParamsSigPeak = "video-params/sig-peak" 9 | static let videoParamsSceneMaxR = "video-params/scene-max-r" 10 | static let videoParamsSceneMaxG = "video-params/scene-max-g" 11 | static let videoParamsSceneMaxB = "video-params/scene-max-b" 12 | static let pause = "pause" 13 | static let pausedForCache = "paused-for-cache" 14 | } 15 | -------------------------------------------------------------------------------- /Demo/Demo-tvOS/Demo-tvOS/Player/Metal/MPVMetalPlayerView.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import SwiftUI 3 | 4 | struct MPVMetalPlayerView: UIViewControllerRepresentable { 5 | @ObservedObject var coordinator: Coordinator 6 | 7 | func makeUIViewController(context: Context) -> some UIViewController { 8 | let mpv = MPVMetalViewController() 9 | mpv.playDelegate = coordinator 10 | mpv.playUrl = coordinator.playUrl 11 | 12 | context.coordinator.player = mpv 13 | return mpv 14 | } 15 | 16 | func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) { 17 | } 18 | 19 | public func makeCoordinator() -> Coordinator { 20 | coordinator 21 | } 22 | 23 | func play(_ url: URL) -> Self { 24 | coordinator.playUrl = url 25 | return self 26 | } 27 | 28 | func onPropertyChange(_ handler: @escaping (MPVMetalViewController, String, Any?) -> Void) -> Self { 29 | coordinator.onPropertyChange = handler 30 | return self 31 | } 32 | 33 | @MainActor 34 | public final class Coordinator: MPVPlayerDelegate, ObservableObject { 35 | weak var player: MPVMetalViewController? 36 | 37 | var playUrl : URL? 38 | var onPropertyChange: ((MPVMetalViewController, String, Any?) -> Void)? 39 | 40 | func play(_ url: URL) { 41 | player?.loadFile(url) 42 | } 43 | 44 | func propertyChange(mpv: OpaquePointer, propertyName: String, data: Any?) { 45 | guard let player else { return } 46 | 47 | self.onPropertyChange?(player, propertyName, data) 48 | } 49 | } 50 | } 51 | 52 | -------------------------------------------------------------------------------- /Demo/Demo-tvOS/Demo-tvOS/Player/Metal/MPVMetalViewController.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import UIKit 3 | import Libmpv 4 | 5 | // warning: metal API validation has been disabled to ignore crash when playing HDR videos. 6 | // Edit Scheme -> Run -> Diagnostics -> Metal API Validation -> Turn it off 7 | // https://github.com/KhronosGroup/MoltenVK/issues/2226 8 | final class MPVMetalViewController: UIViewController { 9 | var metalLayer = MetalLayer() 10 | var mpv: OpaquePointer! 11 | var playDelegate: MPVPlayerDelegate? 12 | lazy var queue = DispatchQueue(label: "mpv", qos: .userInitiated) 13 | 14 | var playUrl: URL? 15 | var hdrAvailable : Bool { 16 | let maxEDRRange = view.window?.screen.potentialEDRHeadroom ?? 1.0 17 | let sigPeak = getDouble(MPVProperty.videoParamsSigPeak) 18 | // display screen support HDR and current playing HDR video 19 | return maxEDRRange > 1.0 && sigPeak > 1.0 20 | } 21 | var hdrEnabled = false { 22 | didSet { 23 | // FIXME: target-colorspace-hint does not support being changed at runtime. 24 | // this option should be set as early as possible otherwise can cause issues 25 | // not recommended to use this way. 26 | if hdrEnabled { 27 | checkError(mpv_set_option_string(mpv, "target-colorspace-hint", "yes")) 28 | } else { 29 | checkError(mpv_set_option_string(mpv, "target-colorspace-hint", "no")) 30 | } 31 | } 32 | } 33 | 34 | override func viewDidLoad() { 35 | super.viewDidLoad() 36 | 37 | metalLayer.frame = view.frame 38 | print(view.bounds) 39 | print(view.frame) 40 | metalLayer.contentsScale = UIScreen.main.nativeScale 41 | metalLayer.framebufferOnly = true 42 | metalLayer.backgroundColor = UIColor.black.cgColor 43 | 44 | view.layer.addSublayer(metalLayer) 45 | 46 | setupMpv() 47 | 48 | if let url = playUrl { 49 | loadFile(url) 50 | } 51 | } 52 | 53 | override func viewDidLayoutSubviews() { 54 | super.viewDidLayoutSubviews() 55 | 56 | metalLayer.frame = view.frame 57 | } 58 | 59 | func setupMpv() { 60 | mpv = mpv_create() 61 | if mpv == nil { 62 | print("failed creating context\n") 63 | exit(1) 64 | } 65 | 66 | // https://mpv.io/manual/stable/#options 67 | #if DEBUG 68 | checkError(mpv_request_log_messages(mpv, "debug")) 69 | #else 70 | checkError(mpv_request_log_messages(mpv, "no")) 71 | #endif 72 | #if os(macOS) 73 | checkError(mpv_set_option_string(mpv, "input-media-keys", "yes")) 74 | #endif 75 | checkError(mpv_set_option(mpv, "wid", MPV_FORMAT_INT64, &metalLayer)) 76 | checkError(mpv_set_option_string(mpv, "subs-match-os-language", "yes")) 77 | checkError(mpv_set_option_string(mpv, "subs-fallback", "yes")) 78 | checkError(mpv_set_option_string(mpv, "vo", "gpu-next")) 79 | checkError(mpv_set_option_string(mpv, "gpu-api", "vulkan")) 80 | //checkError(mpv_set_option_string(mpv, "gpu-context", "moltenvk")) 81 | checkError(mpv_set_option_string(mpv, "hwdec", "videotoolbox")) 82 | checkError(mpv_set_option_string(mpv, "video-rotate", "no")) 83 | 84 | checkError(mpv_set_option_string(mpv, "ytdl", "no")) 85 | // checkError(mpv_set_option_string(mpv, "target-colorspace-hint", "yes")) // HDR passthrough 86 | // checkError(mpv_set_option_string(mpv, "tone-mapping-visualize", "yes")) // only for debugging purposes 87 | // checkError(mpv_set_option_string(mpv, "profile", "fast")) // can fix frame drop in poor device when play 4k 88 | 89 | 90 | checkError(mpv_initialize(mpv)) 91 | 92 | mpv_observe_property(mpv, 0, MPVProperty.videoParamsSigPeak, MPV_FORMAT_DOUBLE) 93 | mpv_observe_property(mpv, 0, MPVProperty.pausedForCache, MPV_FORMAT_FLAG) 94 | mpv_set_wakeup_callback(self.mpv, { (ctx) in 95 | let client = unsafeBitCast(ctx, to: MPVMetalViewController.self) 96 | client.readEvents() 97 | }, UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque())) 98 | } 99 | 100 | 101 | func loadFile( 102 | _ url: URL 103 | ) { 104 | var args = [url.absoluteString] 105 | var options = [String]() 106 | 107 | args.append("replace") 108 | 109 | if !options.isEmpty { 110 | args.append(options.joined(separator: ",")) 111 | } 112 | 113 | command("loadfile", args: args) 114 | } 115 | 116 | func togglePause() { 117 | getFlag(MPVProperty.pause) ? play() : pause() 118 | } 119 | 120 | func play() { 121 | setFlag(MPVProperty.pause, false) 122 | } 123 | 124 | func pause() { 125 | setFlag(MPVProperty.pause, true) 126 | } 127 | 128 | private func getDouble(_ name: String) -> Double { 129 | guard mpv != nil else { return 0.0 } 130 | var data = Double() 131 | mpv_get_property(mpv, name, MPV_FORMAT_DOUBLE, &data) 132 | return data 133 | } 134 | 135 | private func getString(_ name: String) -> String? { 136 | guard mpv != nil else { return nil } 137 | let cstr = mpv_get_property_string(mpv, name) 138 | let str: String? = cstr == nil ? nil : String(cString: cstr!) 139 | mpv_free(cstr) 140 | return str 141 | } 142 | 143 | private func getFlag(_ name: String) -> Bool { 144 | var data = Int64() 145 | mpv_get_property(mpv, name, MPV_FORMAT_FLAG, &data) 146 | return data > 0 147 | } 148 | 149 | private func setFlag(_ name: String, _ flag: Bool) { 150 | guard mpv != nil else { return } 151 | var data: Int = flag ? 1 : 0 152 | mpv_set_property(mpv, name, MPV_FORMAT_FLAG, &data) 153 | } 154 | 155 | 156 | func command( 157 | _ command: String, 158 | args: [String?] = [], 159 | checkForErrors: Bool = true, 160 | returnValueCallback: ((Int32) -> Void)? = nil 161 | ) { 162 | guard mpv != nil else { 163 | return 164 | } 165 | var cargs = makeCArgs(command, args).map { $0.flatMap { UnsafePointer(strdup($0)) } } 166 | defer { 167 | for ptr in cargs where ptr != nil { 168 | free(UnsafeMutablePointer(mutating: ptr!)) 169 | } 170 | } 171 | //print("\(command) -- \(args)") 172 | let returnValue = mpv_command(mpv, &cargs) 173 | if checkForErrors { 174 | checkError(returnValue) 175 | } 176 | if let cb = returnValueCallback { 177 | cb(returnValue) 178 | } 179 | } 180 | 181 | private func makeCArgs(_ command: String, _ args: [String?]) -> [String?] { 182 | if !args.isEmpty, args.last == nil { 183 | fatalError("Command do not need a nil suffix") 184 | } 185 | 186 | var strArgs = args 187 | strArgs.insert(command, at: 0) 188 | strArgs.append(nil) 189 | 190 | return strArgs 191 | } 192 | 193 | func readEvents() { 194 | queue.async { [weak self] in 195 | guard let self else { return } 196 | 197 | while self.mpv != nil { 198 | let event = mpv_wait_event(self.mpv, 0) 199 | if event?.pointee.event_id == MPV_EVENT_NONE { 200 | break 201 | } 202 | 203 | switch event!.pointee.event_id { 204 | case MPV_EVENT_PROPERTY_CHANGE: 205 | let dataOpaquePtr = OpaquePointer(event!.pointee.data) 206 | if let property = UnsafePointer(dataOpaquePtr)?.pointee { 207 | let propertyName = String(cString: property.name) 208 | switch propertyName { 209 | case MPVProperty.pausedForCache: 210 | let buffering = UnsafePointer(OpaquePointer(property.data))?.pointee ?? true 211 | DispatchQueue.main.async { 212 | self.playDelegate?.propertyChange(mpv: self.mpv, propertyName: propertyName, data: buffering) 213 | } 214 | default: break 215 | } 216 | } 217 | case MPV_EVENT_SHUTDOWN: 218 | print("event: shutdown\n"); 219 | mpv_terminate_destroy(mpv); 220 | mpv = nil; 221 | break; 222 | case MPV_EVENT_LOG_MESSAGE: 223 | let msg = UnsafeMutablePointer(OpaquePointer(event!.pointee.data)) 224 | print("[\(String(cString: (msg!.pointee.prefix)!))] \(String(cString: (msg!.pointee.level)!)): \(String(cString: (msg!.pointee.text)!))", terminator: "") 225 | default: 226 | let eventName = mpv_event_name(event!.pointee.event_id ) 227 | print("event: \(String(cString: (eventName)!))"); 228 | } 229 | 230 | } 231 | } 232 | } 233 | 234 | 235 | private func checkError(_ status: CInt) { 236 | if status < 0 { 237 | print("MPV API error: \(String(cString: mpv_error_string(status)))\n") 238 | } 239 | } 240 | 241 | } 242 | -------------------------------------------------------------------------------- /Demo/Demo-tvOS/Demo-tvOS/Player/Metal/MetalLayer.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import UIKit 3 | 4 | class MetalLayer: CAMetalLayer { 5 | 6 | // workaround for a MoltenVK that sets the drawableSize to 1x1 to forcefully complete 7 | // the presentation, this causes flicker and the drawableSize possibly staying at 1x1 8 | // https://github.com/mpv-player/mpv/pull/13651 9 | override var drawableSize: CGSize { 10 | get { return super.drawableSize } 11 | set { 12 | if Int(newValue.width) > 1 && Int(newValue.height) > 1 { 13 | super.drawableSize = newValue 14 | } 15 | } 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /Demo/Demo-tvOS/Demo-tvOS/Player/OpenGL/MPVPlayerView.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import SwiftUI 3 | 4 | struct MPVPlayerView: UIViewControllerRepresentable { 5 | @ObservedObject var coordinator: Coordinator 6 | 7 | func makeUIViewController(context: Context) -> some UIViewController { 8 | let mpv = MPVViewController() 9 | mpv.playDelegate = coordinator 10 | mpv.playUrl = coordinator.playUrl 11 | 12 | context.coordinator.player = mpv 13 | return mpv 14 | } 15 | 16 | func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) { 17 | } 18 | 19 | public func makeCoordinator() -> Coordinator { 20 | coordinator 21 | } 22 | 23 | func play(_ url: URL) -> Self { 24 | coordinator.playUrl = url 25 | return self 26 | } 27 | 28 | func onPropertyChange(_ handler: @escaping (MPVViewController, String, Any?) -> Void) -> Self { 29 | coordinator.onPropertyChange = handler 30 | return self 31 | } 32 | 33 | @MainActor 34 | public final class Coordinator: MPVPlayerDelegate, ObservableObject { 35 | weak var player: MPVViewController? 36 | 37 | var playUrl : URL? 38 | var onPropertyChange: ((MPVViewController, String, Any?) -> Void)? 39 | 40 | func play(_ url: URL) { 41 | player?.loadFile(url) 42 | } 43 | 44 | func propertyChange(mpv: OpaquePointer, propertyName: String, data: Any?) { 45 | guard let player else { return } 46 | 47 | self.onPropertyChange?(player, propertyName, data) 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Demo/Demo-tvOS/Demo-tvOS/Player/OpenGL/MPVViewController.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import GLKit 3 | import Libmpv 4 | 5 | class MPVViewController: GLKViewController { 6 | var mpv: OpaquePointer! 7 | var mpvGL: OpaquePointer! 8 | var playDelegate: MPVPlayerDelegate? 9 | var queue: DispatchQueue = DispatchQueue(label: "mpv", qos: .userInteractive) 10 | private var defaultFBO: GLint = -1 11 | var playUrl: URL? 12 | 13 | 14 | override func viewDidLoad() { 15 | super.viewDidLoad() 16 | 17 | self.setupContext() 18 | self.setupMpv() 19 | 20 | if let url = playUrl { 21 | self.loadFile(url) 22 | } 23 | } 24 | 25 | func setupContext() { 26 | let context = EAGLContext(api: .openGLES2)! 27 | if context == nil { 28 | print("create context fail ...") 29 | return 30 | } 31 | let isSuccess = EAGLContext.setCurrent(context) 32 | if !isSuccess { 33 | print("setup context fail") 34 | } 35 | 36 | let glkView = self.view as! GLKView 37 | glkView.context = context 38 | } 39 | 40 | func setupMpv() { 41 | mpv = mpv_create() 42 | if mpv == nil { 43 | print("failed creating context\n") 44 | exit(1) 45 | } 46 | 47 | // https://mpv.io/manual/stable/#options 48 | #if DEBUG 49 | checkError(mpv_request_log_messages(mpv, "debug")) 50 | #else 51 | checkError(mpv_request_log_messages(mpv, "no")) 52 | #endif 53 | #if os(macOS) 54 | checkError(mpv_set_option_string(mpv, "input-media-keys", "yes")) 55 | #endif 56 | checkError(mpv_set_option_string(mpv, "subs-match-os-language", "yes")) 57 | checkError(mpv_set_option_string(mpv, "subs-fallback", "yes")) 58 | checkError(mpv_set_option_string(mpv, "hwdec", machine == "x86_64" ? "no" : "auto-safe")) 59 | checkError(mpv_set_option_string(mpv, "vo", "libmpv")) 60 | 61 | checkError(mpv_initialize(mpv)) 62 | 63 | let api = UnsafeMutableRawPointer(mutating: (MPV_RENDER_API_TYPE_OPENGL as NSString).utf8String) 64 | var initParams = mpv_opengl_init_params( 65 | get_proc_address: { 66 | (ctx, name) in 67 | return MPVViewController.getProcAddress(ctx, name) 68 | }, 69 | get_proc_address_ctx: nil 70 | ) 71 | 72 | withUnsafeMutablePointer(to: &initParams) { initParams in 73 | var params = [ 74 | mpv_render_param(type: MPV_RENDER_PARAM_API_TYPE, data: api), 75 | mpv_render_param(type: MPV_RENDER_PARAM_OPENGL_INIT_PARAMS, data: initParams), 76 | mpv_render_param() 77 | ] 78 | 79 | if mpv_render_context_create(&mpvGL, mpv, ¶ms) < 0 { 80 | puts("failed to initialize mpv GL context") 81 | exit(1) 82 | } 83 | 84 | mpv_render_context_set_update_callback( 85 | mpvGL, 86 | mpvGLUpdate, 87 | UnsafeMutableRawPointer(Unmanaged.passUnretained(self.view).toOpaque()) 88 | ) 89 | 90 | } 91 | 92 | mpv_observe_property(mpv, 0, MPVProperty.videoParamsSigPeak, MPV_FORMAT_DOUBLE) 93 | mpv_observe_property(mpv, 0, MPVProperty.pausedForCache, MPV_FORMAT_FLAG) 94 | mpv_set_wakeup_callback(self.mpv, { (ctx) in 95 | let client = unsafeBitCast(ctx, to: MPVViewController.self) 96 | client.readEvents() 97 | }, UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque())) 98 | } 99 | 100 | 101 | func loadFile( 102 | _ url: URL 103 | ) { 104 | var args = [url.absoluteString] 105 | var options = [String]() 106 | 107 | args.append("replace") 108 | 109 | if !options.isEmpty { 110 | args.append(options.joined(separator: ",")) 111 | } 112 | 113 | command("loadfile", args: args) 114 | } 115 | 116 | func togglePause() { 117 | getFlag(MPVProperty.pause) ? play() : pause() 118 | } 119 | 120 | func play() { 121 | setFlag(MPVProperty.pause, false) 122 | } 123 | 124 | func pause() { 125 | setFlag(MPVProperty.pause, true) 126 | } 127 | 128 | private func getFlag(_ name: String) -> Bool { 129 | var data = Int64() 130 | mpv_get_property(mpv, name, MPV_FORMAT_FLAG, &data) 131 | return data > 0 132 | } 133 | 134 | private func setFlag(_ name: String, _ flag: Bool) { 135 | guard mpv != nil else { return } 136 | var data: Int = flag ? 1 : 0 137 | mpv_set_property(mpv, name, MPV_FORMAT_FLAG, &data) 138 | } 139 | 140 | func command( 141 | _ command: String, 142 | args: [String?] = [], 143 | checkForErrors: Bool = true, 144 | returnValueCallback: ((Int32) -> Void)? = nil 145 | ) { 146 | guard mpv != nil else { 147 | return 148 | } 149 | var cargs = makeCArgs(command, args).map { $0.flatMap { UnsafePointer(strdup($0)) } } 150 | defer { 151 | for ptr in cargs where ptr != nil { 152 | free(UnsafeMutablePointer(mutating: ptr!)) 153 | } 154 | } 155 | //print("\(command) -- \(args)") 156 | let returnValue = mpv_command(mpv, &cargs) 157 | if checkForErrors { 158 | checkError(returnValue) 159 | } 160 | if let cb = returnValueCallback { 161 | cb(returnValue) 162 | } 163 | } 164 | 165 | private func makeCArgs(_ command: String, _ args: [String?]) -> [String?] { 166 | if !args.isEmpty, args.last == nil { 167 | fatalError("Command do not need a nil suffix") 168 | } 169 | 170 | var strArgs = args 171 | strArgs.insert(command, at: 0) 172 | strArgs.append(nil) 173 | 174 | return strArgs 175 | } 176 | 177 | 178 | func readEvents() { 179 | queue.async { [self] in 180 | while self.mpv != nil { 181 | let event = mpv_wait_event(self.mpv, 0) 182 | if event!.pointee.event_id == MPV_EVENT_NONE { 183 | break 184 | } 185 | switch event!.pointee.event_id { 186 | case MPV_EVENT_PROPERTY_CHANGE: 187 | let dataOpaquePtr = OpaquePointer(event!.pointee.data) 188 | if let property = UnsafePointer(dataOpaquePtr)?.pointee { 189 | let propertyName = String(cString: property.name) 190 | switch propertyName { 191 | case MPVProperty.pausedForCache: 192 | let buffering = UnsafePointer(OpaquePointer(property.data))?.pointee ?? true 193 | DispatchQueue.main.async { 194 | self.playDelegate?.propertyChange(mpv: self.mpv, propertyName: propertyName, data: buffering) 195 | } 196 | default: break 197 | } 198 | } 199 | case MPV_EVENT_SHUTDOWN: 200 | mpv_render_context_free(mpvGL); 201 | mpv_terminate_destroy(mpv); 202 | mpv = nil; 203 | print("event: shutdown\n"); 204 | break; 205 | case MPV_EVENT_LOG_MESSAGE: 206 | let msg = UnsafeMutablePointer(OpaquePointer(event!.pointee.data)) 207 | print("[\(String(cString: (msg!.pointee.prefix)!))] \(String(cString: (msg!.pointee.level)!)): \(String(cString: (msg!.pointee.text)!))", terminator: "") 208 | default: 209 | let eventName = mpv_event_name(event!.pointee.event_id ) 210 | print("event: \(String(cString: (eventName)!))"); 211 | } 212 | } 213 | } 214 | } 215 | 216 | 217 | private func checkError(_ status: CInt) { 218 | if status < 0 { 219 | print("MPV API error: \(String(cString: mpv_error_string(status)))\n") 220 | } 221 | } 222 | 223 | 224 | private var machine: String { 225 | var systeminfo = utsname() 226 | uname(&systeminfo) 227 | return withUnsafeBytes(of: &systeminfo.machine) { bufPtr -> String in 228 | let data = Data(bufPtr) 229 | if let lastIndex = data.lastIndex(where: { $0 != 0 }) { 230 | return String(data: data[0 ... lastIndex], encoding: .isoLatin1)! 231 | } else { 232 | return String(data: data, encoding: .isoLatin1)! 233 | } 234 | } 235 | } 236 | 237 | 238 | override func glkView(_ view: GLKView, drawIn rect: CGRect) { 239 | guard let mpvGL else { 240 | return 241 | } 242 | 243 | // fill black background 244 | glClearColor(0, 0, 0, 0) 245 | glClear(UInt32(GL_COLOR_BUFFER_BIT)) 246 | 247 | glGetIntegerv(UInt32(GL_FRAMEBUFFER_BINDING), &defaultFBO) 248 | 249 | var dims: [GLint] = [0, 0, 0, 0] 250 | glGetIntegerv(GLenum(GL_VIEWPORT), &dims) 251 | 252 | var data = mpv_opengl_fbo( 253 | fbo: Int32(defaultFBO), 254 | w: Int32(dims[2]), 255 | h: Int32(dims[3]), 256 | internal_format: 0 257 | ) 258 | 259 | var flip: CInt = 1 260 | withUnsafeMutablePointer(to: &flip) { flip in 261 | withUnsafeMutablePointer(to: &data) { data in 262 | var params = [ 263 | mpv_render_param(type: MPV_RENDER_PARAM_OPENGL_FBO, data: data), 264 | mpv_render_param(type: MPV_RENDER_PARAM_FLIP_Y, data: flip), 265 | mpv_render_param() 266 | ] 267 | mpv_render_context_render(mpvGL, ¶ms) 268 | } 269 | } 270 | 271 | } 272 | 273 | 274 | private static func getProcAddress(_: UnsafeMutableRawPointer?, _ name: UnsafePointer?) -> UnsafeMutableRawPointer? { 275 | let symbolName = CFStringCreateWithCString(kCFAllocatorDefault, name, CFStringBuiltInEncodings.ASCII.rawValue) 276 | let identifier = CFBundleGetBundleWithIdentifier("com.apple.opengles" as CFString) 277 | 278 | return CFBundleGetFunctionPointerForName(identifier, symbolName) 279 | } 280 | 281 | } 282 | 283 | 284 | 285 | private func mpvGLUpdate(_ ctx: UnsafeMutableRawPointer?) { 286 | let glView = unsafeBitCast(ctx, to: GLKView.self) 287 | 288 | DispatchQueue.main.async { 289 | glView.display() 290 | } 291 | } 292 | -------------------------------------------------------------------------------- /Demo/Demo-tvOS/Demo-tvOS/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Demo/Demo.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Demo/Demo.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # make only accept argument format: xxxx=xxxx, other format will treat as a target. 2 | # add [enable-split-platform enable-debug enable-gpl] to .PHONY can ignore target not exist error. 3 | .PHONY: help build gpl clean enable-split-platform enable-debug enable-gpl 4 | 5 | help: 6 | @echo "Usage: make [target]" 7 | @echo "" 8 | @echo "Targets:" 9 | @echo " build [arguments] Build the project for iOS and macOS" 10 | @echo " Arguments:" 11 | @echo " platform=ios,macos Only build specified platform (ios,macos,tvos,tvsimulator,isimulator,maccatalyst,xros,xrsimulator)" 12 | @echo " enable-gpl Complile to GPL version" 13 | @echo " clean Clean the build artifacts" 14 | @echo " help Display this help message" 15 | 16 | build: 17 | swift run --build-path ./.build --package-path Sources/BuildScripts build $(filter-out $@,$(MAKECMDGOALS)) $(MAKEFLAGS) 18 | 19 | gpl: 20 | swift run --build-path ./.build --package-path Sources/BuildScripts build enable-gpl $(filter-out $@,$(MAKECMDGOALS)) $(MAKEFLAGS) 21 | 22 | clean: 23 | @find . -name '.build' -type d -exec rm -rf {} + 24 | @find . -name '.swiftpm' -type d -exec rm -rf {} + 25 | @rm -rf ./dist 26 | @rm -rf ./*.log 27 | @swift package reset -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.8 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "MPVKit", 7 | platforms: [.macOS(.v10_15), .iOS(.v13), .tvOS(.v13)], 8 | products: [ 9 | .library( 10 | name: "MPVKit", 11 | targets: ["_MPVKit"] 12 | ), 13 | .library( 14 | name: "MPVKit-GPL", 15 | targets: ["_MPVKit-GPL"] 16 | ), 17 | ], 18 | targets: [ 19 | .target( 20 | name: "_MPVKit", 21 | dependencies: [ 22 | "Libmpv", "_FFmpeg", "Libuchardet", "Libbluray", 23 | .target(name: "Libluajit", condition: .when(platforms: [.macOS])), 24 | ], 25 | path: "Sources/_MPVKit", 26 | linkerSettings: [ 27 | .linkedFramework("AVFoundation"), 28 | .linkedFramework("CoreAudio"), 29 | ] 30 | ), 31 | .target( 32 | name: "_FFmpeg", 33 | dependencies: [ 34 | "Libavcodec", "Libavdevice", "Libavfilter", "Libavformat", "Libavutil", "Libswresample", "Libswscale", 35 | "Libssl", "Libcrypto", "Libass", "Libfreetype", "Libfribidi", "Libharfbuzz", 36 | "MoltenVK", "Libshaderc_combined", "lcms2", "Libplacebo", "Libdovi", "Libunibreak", 37 | "gmp", "nettle", "hogweed", "gnutls", "Libdav1d", "Libuavs3d" 38 | ], 39 | path: "Sources/_FFmpeg", 40 | linkerSettings: [ 41 | .linkedFramework("AudioToolbox"), 42 | .linkedFramework("CoreVideo"), 43 | .linkedFramework("CoreFoundation"), 44 | .linkedFramework("CoreMedia"), 45 | .linkedFramework("Metal"), 46 | .linkedFramework("VideoToolbox"), 47 | .linkedLibrary("bz2"), 48 | .linkedLibrary("iconv"), 49 | .linkedLibrary("expat"), 50 | .linkedLibrary("resolv"), 51 | .linkedLibrary("xml2"), 52 | .linkedLibrary("z"), 53 | .linkedLibrary("c++"), 54 | ] 55 | ), 56 | .target( 57 | name: "_MPVKit-GPL", 58 | dependencies: [ 59 | "Libmpv-GPL", "_FFmpeg-GPL", "Libuchardet", "Libbluray", 60 | .target(name: "Libluajit", condition: .when(platforms: [.macOS])), 61 | ], 62 | path: "Sources/_MPVKit-GPL", 63 | linkerSettings: [ 64 | .linkedFramework("AVFoundation"), 65 | .linkedFramework("CoreAudio"), 66 | ] 67 | ), 68 | .target( 69 | name: "_FFmpeg-GPL", 70 | dependencies: [ 71 | "Libavcodec-GPL", "Libavdevice-GPL", "Libavfilter-GPL", "Libavformat-GPL", "Libavutil-GPL", "Libswresample-GPL", "Libswscale-GPL", 72 | "Libssl", "Libcrypto", "Libass", "Libfreetype", "Libfribidi", "Libharfbuzz", 73 | "MoltenVK", "Libshaderc_combined", "lcms2", "Libplacebo", "Libdovi", "Libunibreak", 74 | "Libsmbclient", "gmp", "nettle", "hogweed", "gnutls", "Libdav1d", "Libuavs3d" 75 | ], 76 | path: "Sources/_FFmpeg-GPL", 77 | linkerSettings: [ 78 | .linkedFramework("AudioToolbox"), 79 | .linkedFramework("CoreVideo"), 80 | .linkedFramework("CoreFoundation"), 81 | .linkedFramework("CoreMedia"), 82 | .linkedFramework("Metal"), 83 | .linkedFramework("VideoToolbox"), 84 | .linkedLibrary("bz2"), 85 | .linkedLibrary("iconv"), 86 | .linkedLibrary("expat"), 87 | .linkedLibrary("resolv"), 88 | .linkedLibrary("xml2"), 89 | .linkedLibrary("z"), 90 | .linkedLibrary("c++"), 91 | ] 92 | ), 93 | 94 | .binaryTarget( 95 | name: "Libmpv-GPL", 96 | url: "https://github.com/mpvkit/MPVKit/releases/download/0.40.0/Libmpv-GPL.xcframework.zip", 97 | checksum: "58b0f73efeb0916805492aa700b2c4df2b113d393222d2ed0eae0aecc120f4f8" 98 | ), 99 | .binaryTarget( 100 | name: "Libavcodec-GPL", 101 | url: "https://github.com/mpvkit/MPVKit/releases/download/0.40.0/Libavcodec-GPL.xcframework.zip", 102 | checksum: "a290d492abfab0b1e66652f3780343ba1a593f294e5c94a3b991d889c72c45d7" 103 | ), 104 | .binaryTarget( 105 | name: "Libavdevice-GPL", 106 | url: "https://github.com/mpvkit/MPVKit/releases/download/0.40.0/Libavdevice-GPL.xcframework.zip", 107 | checksum: "79c9292d1caf86ae303ae45d5643e7e0e83a9f63318f04fe420da6b41a065805" 108 | ), 109 | .binaryTarget( 110 | name: "Libavformat-GPL", 111 | url: "https://github.com/mpvkit/MPVKit/releases/download/0.40.0/Libavformat-GPL.xcframework.zip", 112 | checksum: "40286166d9f15c675566faf685d05fe3928d25263f7a46dab71f5535a34e7990" 113 | ), 114 | .binaryTarget( 115 | name: "Libavfilter-GPL", 116 | url: "https://github.com/mpvkit/MPVKit/releases/download/0.40.0/Libavfilter-GPL.xcframework.zip", 117 | checksum: "2e5a3f804a55827f090938c60ff77102ed841eab84c8c6f6aad751db0f5fdbe1" 118 | ), 119 | .binaryTarget( 120 | name: "Libavutil-GPL", 121 | url: "https://github.com/mpvkit/MPVKit/releases/download/0.40.0/Libavutil-GPL.xcframework.zip", 122 | checksum: "91440f6d743ddf2c828793ac9e0bc6ec8e11efa55d77701de9fb0276ff1e41d1" 123 | ), 124 | .binaryTarget( 125 | name: "Libswresample-GPL", 126 | url: "https://github.com/mpvkit/MPVKit/releases/download/0.40.0/Libswresample-GPL.xcframework.zip", 127 | checksum: "f4ba0a308652291e7da76f5686c35e104034df4e5e6f584358ffea103ab4541b" 128 | ), 129 | .binaryTarget( 130 | name: "Libswscale-GPL", 131 | url: "https://github.com/mpvkit/MPVKit/releases/download/0.40.0/Libswscale-GPL.xcframework.zip", 132 | checksum: "8d696c6cb9278ce9bb4c861c37d8fb7fb851c5625757d5b6920b6d60d1d0fba9" 133 | ), 134 | //AUTO_GENERATE_TARGETS_BEGIN// 135 | 136 | .binaryTarget( 137 | name: "Libcrypto", 138 | url: "https://github.com/mpvkit/openssl-build/releases/download/3.2.0/Libcrypto.xcframework.zip", 139 | checksum: "89989ea14f7297d98083eb8adfba9b389f5b4886cb54fb3d5b6e8b915b7adf1d" 140 | ), 141 | .binaryTarget( 142 | name: "Libssl", 143 | url: "https://github.com/mpvkit/openssl-build/releases/download/3.2.0/Libssl.xcframework.zip", 144 | checksum: "46ad8e8fa5a6efe7bd31c9b3c56b20c1bc29a581083588d86e941d261d7dbe98" 145 | ), 146 | 147 | .binaryTarget( 148 | name: "gmp", 149 | url: "https://github.com/mpvkit/gnutls-build/releases/download/3.8.3/gmp.xcframework.zip", 150 | checksum: "defd5623e6786543588001b8f33026395960a561540738deb6df6996d39f957d" 151 | ), 152 | 153 | .binaryTarget( 154 | name: "nettle", 155 | url: "https://github.com/mpvkit/gnutls-build/releases/download/3.8.3/nettle.xcframework.zip", 156 | checksum: "c3b8f506fa32bcb3f9bf65096b0556c4f5973f846ee964577d783edbe8e6969d" 157 | ), 158 | .binaryTarget( 159 | name: "hogweed", 160 | url: "https://github.com/mpvkit/gnutls-build/releases/download/3.8.3/hogweed.xcframework.zip", 161 | checksum: "47a34e7877f7ebd9175f5645059030e553276faa9a21b91e29fb7463b94e8daf" 162 | ), 163 | 164 | .binaryTarget( 165 | name: "gnutls", 166 | url: "https://github.com/mpvkit/gnutls-build/releases/download/3.8.3/gnutls.xcframework.zip", 167 | checksum: "5f5cf903a2d52157c29ad304260709f618ce086afea02061241982f8425a6fb0" 168 | ), 169 | 170 | .binaryTarget( 171 | name: "Libunibreak", 172 | url: "https://github.com/mpvkit/libass-build/releases/download/0.17.3/Libunibreak.xcframework.zip", 173 | checksum: "430ed1a8ff1a230bd93b6868021cde2aafb23c8cb2d586525836cac47c4f310f" 174 | ), 175 | 176 | .binaryTarget( 177 | name: "Libfreetype", 178 | url: "https://github.com/mpvkit/libass-build/releases/download/0.17.3/Libfreetype.xcframework.zip", 179 | checksum: "300d2966c914e06f0211d8da0ea6208a345709b888e9cbbf1cdd94df26330359" 180 | ), 181 | 182 | .binaryTarget( 183 | name: "Libfribidi", 184 | url: "https://github.com/mpvkit/libass-build/releases/download/0.17.3/Libfribidi.xcframework.zip", 185 | checksum: "4a3122a2027f021937ed0fa07173dca7f2c1c4c4202b7caf8043fa80408c0953" 186 | ), 187 | 188 | .binaryTarget( 189 | name: "Libharfbuzz", 190 | url: "https://github.com/mpvkit/libass-build/releases/download/0.17.3/Libharfbuzz.xcframework.zip", 191 | checksum: "f607773598caa72435d8b19ce6a9d54fa7f26cde126b6b66c0a3d2804f084c68" 192 | ), 193 | 194 | .binaryTarget( 195 | name: "Libass", 196 | url: "https://github.com/mpvkit/libass-build/releases/download/0.17.3/Libass.xcframework.zip", 197 | checksum: "af24cd1429069153f0ca5c650e0b374c27ae38283aca47cbbbc9edb3165b182e" 198 | ), 199 | 200 | .binaryTarget( 201 | name: "Libsmbclient", 202 | url: "https://github.com/mpvkit/libsmbclient-build/releases/download/4.15.13/Libsmbclient.xcframework.zip", 203 | checksum: "589db9c241e6cc274f2825bee542add273febd0666ebd7ea8a402574ed76e9af" 204 | ), 205 | 206 | .binaryTarget( 207 | name: "Libbluray", 208 | url: "https://github.com/mpvkit/libbluray-build/releases/download/1.3.4/Libbluray.xcframework.zip", 209 | checksum: "68540747670e734e9b9063da3e5ccb139d34e8b40e1d5ec3177392603d93dfec" 210 | ), 211 | 212 | .binaryTarget( 213 | name: "Libuavs3d", 214 | url: "https://github.com/mpvkit/libuavs3d-build/releases/download/1.2.1/Libuavs3d.xcframework.zip", 215 | checksum: "893257fc73c61b87fb45ee9de7df6ac4a6967062d7cac2c8d136cd2774a04971" 216 | ), 217 | 218 | .binaryTarget( 219 | name: "Libdovi", 220 | url: "https://github.com/mpvkit/libdovi-build/releases/download/3.3.0/Libdovi.xcframework.zip", 221 | checksum: "ca4382ea4e17103fbcc979d0ddee69a6eb8967c0ab235cb786ffa96da5f512ed" 222 | ), 223 | 224 | .binaryTarget( 225 | name: "MoltenVK", 226 | url: "https://github.com/mpvkit/moltenvk-build/releases/download/1.2.9-fix/MoltenVK.xcframework.zip", 227 | checksum: "63836d61deceb5721ff0790dac651890e44ef770ab7b971fb83cc1b2524d1025" 228 | ), 229 | 230 | .binaryTarget( 231 | name: "Libshaderc_combined", 232 | url: "https://github.com/mpvkit/libshaderc-build/releases/download/2024.2.0/Libshaderc_combined.xcframework.zip", 233 | checksum: "1ccd9fce68ea29af030dceb824716fc16d1f4dcdc0b912ba366d5cb91d7b1add" 234 | ), 235 | 236 | .binaryTarget( 237 | name: "lcms2", 238 | url: "https://github.com/mpvkit/libplacebo-build/releases/download/7.349.0/lcms2.xcframework.zip", 239 | checksum: "bd2c27366f8b7adfe7bf652a922599891c55b82f5c519bcc4eece1ccff57c889" 240 | ), 241 | 242 | .binaryTarget( 243 | name: "Libplacebo", 244 | url: "https://github.com/mpvkit/libplacebo-build/releases/download/7.349.0/Libplacebo.xcframework.zip", 245 | checksum: "f32d20351289a080cd7288742747cd927553fde8c217f63263b838083d07a01a" 246 | ), 247 | 248 | .binaryTarget( 249 | name: "Libdav1d", 250 | url: "https://github.com/mpvkit/libdav1d-build/releases/download/1.4.3/Libdav1d.xcframework.zip", 251 | checksum: "eccfe37da9418e350bc6c1566890fa5b9585fbb87b8ceb664de77800ef17fe04" 252 | ), 253 | 254 | .binaryTarget( 255 | name: "Libavcodec", 256 | url: "https://github.com/mpvkit/MPVKit/releases/download/0.40.0/Libavcodec.xcframework.zip", 257 | checksum: "372483586459f5fd84ab35773490babbc0606ed0a971843fbf48af528598611c" 258 | ), 259 | .binaryTarget( 260 | name: "Libavdevice", 261 | url: "https://github.com/mpvkit/MPVKit/releases/download/0.40.0/Libavdevice.xcframework.zip", 262 | checksum: "4e4e9a28a746450c2d56f50142245d1462c20f5edd0dfd3772e8e3d9e127d781" 263 | ), 264 | .binaryTarget( 265 | name: "Libavformat", 266 | url: "https://github.com/mpvkit/MPVKit/releases/download/0.40.0/Libavformat.xcframework.zip", 267 | checksum: "f0aaa2993be378c7d00a0b065d8b5c8d0ec34bc6217deed7f7c2afea82602a0e" 268 | ), 269 | .binaryTarget( 270 | name: "Libavfilter", 271 | url: "https://github.com/mpvkit/MPVKit/releases/download/0.40.0/Libavfilter.xcframework.zip", 272 | checksum: "7cb0c95dbaab1a1f7c59529d988abbfc2b003ded943cd009a386c85b90ea365d" 273 | ), 274 | .binaryTarget( 275 | name: "Libavutil", 276 | url: "https://github.com/mpvkit/MPVKit/releases/download/0.40.0/Libavutil.xcframework.zip", 277 | checksum: "284155befb18262eda177868784336caa1c8117d0a6bdf8cd1fda4a590988646" 278 | ), 279 | .binaryTarget( 280 | name: "Libswresample", 281 | url: "https://github.com/mpvkit/MPVKit/releases/download/0.40.0/Libswresample.xcframework.zip", 282 | checksum: "16b31d138230ff145808b735b78329c8cbae7b1c078f8db8cf660203cf2f5231" 283 | ), 284 | .binaryTarget( 285 | name: "Libswscale", 286 | url: "https://github.com/mpvkit/MPVKit/releases/download/0.40.0/Libswscale.xcframework.zip", 287 | checksum: "d58699780938475eb98cdae67c4ed1e749aef9ae3f4b9599f5f5fd129244fbbf" 288 | ), 289 | 290 | .binaryTarget( 291 | name: "Libuchardet", 292 | url: "https://github.com/mpvkit/libuchardet-build/releases/download/0.0.8/Libuchardet.xcframework.zip", 293 | checksum: "80b14d8080c2531ced6d6b234a826c13f0be459a8c751815f68e0eefd34afa30" 294 | ), 295 | 296 | .binaryTarget( 297 | name: "Libluajit", 298 | url: "https://github.com/mpvkit/libluajit-build/releases/download/2.1.0/Libluajit.xcframework.zip", 299 | checksum: "3765d7c6392b4f9a945b334ed593747b8adb9345078717ecfb6d7d12114a0f9e" 300 | ), 301 | 302 | .binaryTarget( 303 | name: "Libmpv", 304 | url: "https://github.com/mpvkit/MPVKit/releases/download/0.40.0/Libmpv.xcframework.zip", 305 | checksum: "4092cf0fdcc843ec9295383374b4a0236965b5dd834ba791841615441bd34481" 306 | ), 307 | //AUTO_GENERATE_TARGETS_END// 308 | ] 309 | ) 310 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MPVKit 2 | 3 | [![mpv](https://img.shields.io/badge/mpv-v0.40.0-blue.svg)](https://github.com/mpv-player/mpv) 4 | [![ffmpeg](https://img.shields.io/badge/ffmpeg-n7.1.1-blue.svg)](https://github.com/FFmpeg/FFmpeg) 5 | [![license](https://img.shields.io/github/license/mpvkit/MPVKit)](https://github.com/mpvkit/MPVKit/main/LICENSE) 6 | 7 | > MPVKit is only suitable for learning `libmpv` and will not be maintained too frequently. 8 | 9 | `MPVKit` is a collection of tools to use `mpv` in `iOS`, `macOS`, `tvOS` applications. 10 | 11 | It includes scripts to build `mpv` native libraries. 12 | 13 | Forked from [kingslay/FFmpegKit](https://github.com/kingslay/FFmpegKit) 14 | 15 | ## About Metal support 16 | 17 | Metal support only a patch version ([#7857](https://github.com/mpv-player/mpv/pull/7857)) and does not officially support it yet. Encountering any issues is not strange. 18 | 19 | ## Installation 20 | 21 | ### Swift Package Manager 22 | 23 | ``` 24 | https://github.com/mpvkit/MPVKit.git 25 | ``` 26 | 27 | ### Choose which version 28 | 29 | | Version | License | Note | 30 | |---|---|---| 31 | | MPVKit | LGPL | [FFmpeg details](https://github.com/FFmpeg/FFmpeg/blob/master/LICENSE.md) , [mpv details](https://github.com/mpv-player/mpv/blob/master/Copyright) | 32 | | MPVKit-GPL | GPL | Support samba protocol, same as old MPVKit version | 33 | 34 | 35 | ## How to build 36 | 37 | ```bash 38 | make build 39 | # specified platforms (ios,macos,tvos,tvsimulator,isimulator,maccatalyst,xros,xrsimulator) 40 | make build platform=ios,macos 41 | # build GPL version 42 | make build enable-gpl 43 | # clean all build temp files and cache 44 | make clean 45 | # see help 46 | make help 47 | ``` 48 | 49 | ## Make demo app using the local build version 50 | 51 | If you want the demo app to use the local build version, you need to modify `Package.swift` to reference the local build xcframework file. 52 | 53 |
54 | Click here for more information. 55 | 56 | ``` 57 | .binaryTarget( 58 | name: "Libmpv-GPL", 59 | path: "dist/release/Libmpv.xcframework.zip" 60 | ), 61 | .binaryTarget( 62 | name: "Libavcodec-GPL", 63 | path: "dist/release/Libavcodec.xcframework.zip" 64 | ), 65 | .binaryTarget( 66 | name: "Libavdevice-GPL", 67 | path: "dist/release/Libavdevice.xcframework.zip" 68 | ), 69 | .binaryTarget( 70 | name: "Libavformat-GPL", 71 | path: "dist/release/Libavformat.xcframework.zip" 72 | ), 73 | .binaryTarget( 74 | name: "Libavfilter-GPL", 75 | path: "dist/release/Libavfilter.xcframework.zip" 76 | ), 77 | .binaryTarget( 78 | name: "Libavutil-GPL", 79 | path: "dist/release/Libavutil.xcframework.zip" 80 | ), 81 | .binaryTarget( 82 | name: "Libswresample-GPL", 83 | path: "dist/release/Libswresample.xcframework.zip" 84 | ), 85 | .binaryTarget( 86 | name: "Libswscale-GPL", 87 | path: "dist/release/Libswscale.xcframework.zip" 88 | ), 89 | ``` 90 | 91 |
92 | 93 | ## Run default mpv player 94 | 95 | ```bash 96 | ./mpv.sh --input-commands='script-message display-stats-toggle' [url] 97 | ./mpv.sh --list-options 98 | ``` 99 | 100 | > Use Shift+i to show stats overlay 101 | 102 | ## Related Projects 103 | 104 | * [moltenvk-build](https://github.com/mpvkit/moltenvk-build) 105 | * [libplacebo-build](https://github.com/mpvkit/libplacebo-build) 106 | * [libdovi-build](https://github.com/mpvkit/libdovi-build) 107 | * [libshaderc-build](https://github.com/mpvkit/libshaderc-build) 108 | * [libluajit-build](https://github.com/mpvkit/libluajit-build) 109 | * [libass-build](https://github.com/mpvkit/libass-build) 110 | * [libbluray-build](https://github.com/mpvkit/libbluray-build) 111 | * [libsmbclient-build](https://github.com/mpvkit/libsmbclient-build) 112 | * [gnutls-build](https://github.com/mpvkit/gnutls-build) 113 | * [openssl-build](https://github.com/mpvkit/openssl-build) 114 | 115 | ## Donation 116 | 117 | If you appreciate my current work, you can buy me a cup of coffee ☕️. 118 | 119 | [![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/C0C410P7UN) 120 | 121 | ## License 122 | 123 | `MPVKit` source alone is licensed under the LGPL v3.0. 124 | 125 | `MPVKit` bundles (`frameworks`, `xcframeworks`), which include both `libmpv` and `FFmpeg` libraries, are also licensed under the LGPL v3.0. However, if the source code is built using the optional `enable-gpl` flag or prebuilt binaries with `-GPL` postfix are used, then `MPVKit` bundles become subject to the GPL v3.0. 126 | -------------------------------------------------------------------------------- /Sources/BuildScripts/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.8 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "build", 7 | products: [ 8 | ], 9 | targets: [ 10 | .executableTarget( 11 | name: "build", 12 | path: "XCFrameworkBuild" 13 | ) 14 | ] 15 | ) 16 | -------------------------------------------------------------------------------- /Sources/BuildScripts/patch/FFmpeg/0001-hls-seek-patch-1.patch: -------------------------------------------------------------------------------- 1 | From 0c14ac8d5f19f22f31ae9505e3db5eb417e8d6e0 Mon Sep 17 00:00:00 2001 2 | From: llyyr 3 | Date: Sun, 27 Oct 2024 06:52:42 +0530 4 | Subject: [PATCH 1/2] avformat/hls: always return keyframe if not 5 | AVSEEK_FLAG_ANY 6 | 7 | Co-Authored-by: vectronic 8 | --- 9 | libavformat/hls.c | 8 +++++--- 10 | 1 file changed, 5 insertions(+), 3 deletions(-) 11 | 12 | diff --git a/libavformat/hls.c b/libavformat/hls.c 13 | index 62473a15ddb5..4d02faa9e49a 100644 14 | --- a/libavformat/hls.c 15 | +++ b/libavformat/hls.c 16 | @@ -2350,8 +2350,10 @@ static int hls_read_packet(AVFormatContext *s, AVPacket *pkt) 17 | ts_diff = av_rescale_rnd(pls->pkt->dts, AV_TIME_BASE, 18 | tb.den, AV_ROUND_DOWN) - 19 | pls->seek_timestamp; 20 | - if (ts_diff >= 0 && (pls->seek_flags & AVSEEK_FLAG_ANY || 21 | - pls->pkt->flags & AV_PKT_FLAG_KEY)) { 22 | + /* If AVSEEK_FLAG_ANY, keep reading until ts_diff >= 0, 23 | + * otherwise return the first keyframe encountered */ 24 | + if ((ts_diff >= 0 && (pls->seek_flags & AVSEEK_FLAG_ANY)) || 25 | + (!(pls->seek_flags & AVSEEK_FLAG_ANY) && (pls->pkt->flags & AV_PKT_FLAG_KEY))) { 26 | pls->seek_timestamp = AV_NOPTS_VALUE; 27 | break; 28 | } 29 | @@ -2502,7 +2504,7 @@ static int hls_read_seek(AVFormatContext *s, int stream_index, 30 | pb->eof_reached = 0; 31 | /* Clear any buffered data */ 32 | pb->buf_end = pb->buf_ptr = pb->buffer; 33 | - /* Reset the pos, to let the mpegts demuxer know we've seeked. */ 34 | + /* Reset the pos, to let the mpegts/mov demuxer know we've seeked. */ 35 | pb->pos = 0; 36 | /* Flush the packet queue of the subdemuxer. */ 37 | ff_read_frame_flush(pls->ctx); 38 | -- 39 | 2.47.0 -------------------------------------------------------------------------------- /Sources/BuildScripts/patch/FFmpeg/0002-hls-seek-patch-2.patch: -------------------------------------------------------------------------------- 1 | From 07045b8245cfc5822005c716db8a84b710579787 Mon Sep 17 00:00:00 2001 2 | From: llyyr 3 | Date: Sun, 27 Oct 2024 06:44:51 +0530 4 | Subject: [PATCH 2/2] avformat/mov: handle stream position resets 5 | 6 | If the stream position has been reset, clear fragment index, the index 7 | entries and the current sample and search for the next root. 8 | 9 | Co-Authored-by: vectronic 10 | --- 11 | libavformat/mov.c | 37 +++++++++++++++++++++++++++++++++---- 12 | 1 file changed, 33 insertions(+), 4 deletions(-) 13 | 14 | diff --git a/libavformat/mov.c b/libavformat/mov.c 15 | index 8c3329b81596..78540bec1d11 100644 16 | --- a/libavformat/mov.c 17 | +++ b/libavformat/mov.c 18 | @@ -10612,15 +10612,15 @@ static int mov_switch_root(AVFormatContext *s, int64_t target, int index) 19 | 20 | if (index >= 0 && index < mov->frag_index.nb_items) 21 | target = mov->frag_index.item[index].moof_offset; 22 | - if (avio_seek(s->pb, target, SEEK_SET) != target) { 23 | + if (target >= 0 && avio_seek(s->pb, target, SEEK_SET) != target) { 24 | av_log(mov->fc, AV_LOG_ERROR, "root atom offset 0x%"PRIx64": partial file\n", target); 25 | return AVERROR_INVALIDDATA; 26 | } 27 | 28 | mov->next_root_atom = 0; 29 | - if (index < 0 || index >= mov->frag_index.nb_items) 30 | + if ((index < 0 && target >= 0) || index >= mov->frag_index.nb_items) 31 | index = search_frag_moof_offset(&mov->frag_index, target); 32 | - if (index < mov->frag_index.nb_items && 33 | + if (index >= 0 && index < mov->frag_index.nb_items && 34 | mov->frag_index.item[index].moof_offset == target) { 35 | if (index + 1 < mov->frag_index.nb_items) 36 | mov->next_root_atom = mov->frag_index.item[index + 1].moof_offset; 37 | @@ -10751,9 +10751,38 @@ static int mov_read_packet(AVFormatContext *s, AVPacket *pkt) 38 | AVIndexEntry *sample; 39 | AVStream *st = NULL; 40 | int64_t current_index; 41 | - int ret; 42 | + int ret, i; 43 | mov->fc = s; 44 | retry: 45 | + if (s->pb->pos == 0) { 46 | + // Discard current fragment index 47 | + if (mov->frag_index.allocated_size > 0) { 48 | + av_freep(&mov->frag_index.item); 49 | + mov->frag_index.nb_items = 0; 50 | + mov->frag_index.allocated_size = 0; 51 | + mov->frag_index.current = -1; 52 | + mov->frag_index.complete = 0; 53 | + } 54 | + 55 | + for (i = 0; i < s->nb_streams; i++) { 56 | + AVStream *avst = s->streams[i]; 57 | + FFStream *sti = ffstream(avst); 58 | + MOVStreamContext *msc = avst->priv_data; 59 | + 60 | + // Clear current sample 61 | + mov_current_sample_set(msc, 0); 62 | + 63 | + // Discard current index entries 64 | + if (sti->index_entries_allocated_size > 0) { 65 | + av_freep(&sti->index_entries); 66 | + sti->index_entries_allocated_size = 0; 67 | + sti->nb_index_entries = 0; 68 | + } 69 | + } 70 | + 71 | + if ((ret = mov_switch_root(s, -1, -1)) < 0) 72 | + return ret; 73 | + } 74 | sample = mov_find_next_sample(s, &st); 75 | if (!sample || (mov->next_root_atom && sample->pos > mov->next_root_atom)) { 76 | if (!mov->next_root_atom) 77 | -- 78 | 2.47.0 -------------------------------------------------------------------------------- /Sources/BuildScripts/patch/libmpv/0001-player-add-moltenvk-context.patch: -------------------------------------------------------------------------------- 1 | diff --git forkSrcPrefix/meson.build forkDstPrefix/meson.build 2 | index 7572769e0e6f280391668c6797d67e37e2dea30f..e6109987a3d38025a5eede04441c03ee23413255 100644 3 | --- forkSrcPrefix/meson.build 4 | +++ forkDstPrefix/meson.build 5 | @@ -1315,6 +1315,17 @@ if features['vulkan'] and features['x11'] 6 | sources += files('video/out/vulkan/context_xlib.c') 7 | endif 8 | 9 | +if host_machine.system() == 'darwin' 10 | + moltenvk = get_option('moltenvk').require( 11 | + features['vulkan'], 12 | + error_message: 'vulkan or moltenvk header could not be found!', 13 | + ) 14 | + features += {'moltenvk': moltenvk.allowed()} 15 | + if features['vulkan'] and features['moltenvk'] 16 | + sources += files('video/out/vulkan/context_moltenvk.m') 17 | + endif 18 | +endif 19 | + 20 | features += {'vk-khr-display': vulkan.type_name() == 'internal' or 21 | cc.has_function('vkCreateDisplayPlaneSurfaceKHR', prefix: '#include ', 22 | dependencies: [vulkan])} 23 | diff --git forkSrcPrefix/meson.options forkDstPrefix/meson.options 24 | index dae0a333ef71b75a6bb36a236de7feebd99bda40..6ed28af20a09501b4997ca81e86bb198e75327fd 100644 25 | --- forkSrcPrefix/meson.options 26 | +++ forkDstPrefix/meson.options 27 | @@ -101,6 +101,7 @@ option('gl-dxinterop-d3d9', type: 'feature', value: 'auto', description: 'OpenGL 28 | option('ios-gl', type: 'feature', value: 'auto', description: 'iOS OpenGL ES interop support') 29 | option('videotoolbox-gl', type: 'feature', value: 'auto', description: 'Videotoolbox with OpenGL') 30 | option('videotoolbox-pl', type: 'feature', value: 'auto', description: 'Videotoolbox with libplacebo') 31 | +option('moltenvk', type: 'feature', value: 'auto', description: 'Moltenvk context') 32 | 33 | # macOS features 34 | option('macos-10-15-4-features', type: 'feature', value: 'auto', description: 'macOS 10.15.4 SDK Features') 35 | diff --git forkSrcPrefix/video/out/vulkan/context_moltenvk.m forkDstPrefix/video/out/vulkan/context_moltenvk.m 36 | new file mode 100644 37 | index 0000000000000000000000000000000000000000..445b907f795b24b8df80389394d4ae7b3f94c6e5 38 | --- /dev/null 39 | +++ forkDstPrefix/video/out/vulkan/context_moltenvk.m 40 | @@ -0,0 +1,96 @@ 41 | +/* 42 | + * This file is part of mpv. 43 | + * 44 | + * mpv is free software; you can redistribute it and/or 45 | + * modify it under the terms of the GNU Lesser General Public 46 | + * License as published by the Free Software Foundation; either 47 | + * version 2.1 of the License, or (at your option) any later version. 48 | + * 49 | + * mpv is distributed in the hope that it will be useful, 50 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of 51 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 52 | + * GNU Lesser General Public License for more details. 53 | + * 54 | + * You should have received a copy of the GNU Lesser General Public 55 | + * License along with mpv. If not, see . 56 | + */ 57 | + 58 | +#include 59 | +#include 60 | +#include 61 | + 62 | +#include "common.h" 63 | +#include "context.h" 64 | +#include "utils.h" 65 | + 66 | +struct priv { 67 | + struct mpvk_ctx vk; 68 | + CAMetalLayer *layer; 69 | +}; 70 | + 71 | +static void moltenvk_uninit(struct ra_ctx *ctx) 72 | +{ 73 | + struct priv *p = ctx->priv; 74 | + ra_vk_ctx_uninit(ctx); 75 | + mpvk_uninit(&p->vk); 76 | +} 77 | + 78 | +static bool moltenvk_init(struct ra_ctx *ctx) 79 | +{ 80 | + struct priv *p = ctx->priv = talloc_zero(ctx, struct priv); 81 | + struct mpvk_ctx *vk = &p->vk; 82 | + int msgl = ctx->opts.probing ? MSGL_V : MSGL_ERR; 83 | + 84 | + if (ctx->vo->opts->WinID == -1) { 85 | + MP_MSG(ctx, msgl, "WinID missing\n"); 86 | + goto fail; 87 | + } 88 | + 89 | + if (!mpvk_init(vk, ctx, VK_EXT_METAL_SURFACE_EXTENSION_NAME)) 90 | + goto fail; 91 | + 92 | + p->layer = (__bridge CAMetalLayer *)(intptr_t)ctx->vo->opts->WinID; 93 | + VkMetalSurfaceCreateInfoEXT info = { 94 | + .sType = VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT, 95 | + .pLayer = p->layer, 96 | + }; 97 | + 98 | + struct ra_ctx_params params = {0}; 99 | + 100 | + VkInstance inst = vk->vkinst->instance; 101 | + VkResult res = vkCreateMetalSurfaceEXT(inst, &info, NULL, &vk->surface); 102 | + if (res != VK_SUCCESS) { 103 | + MP_MSG(ctx, msgl, "Failed creating MoltenVK surface\n"); 104 | + goto fail; 105 | + } 106 | + 107 | + if (!ra_vk_ctx_init(ctx, vk, params, VK_PRESENT_MODE_FIFO_KHR)) 108 | + goto fail; 109 | + 110 | + return true; 111 | +fail: 112 | + moltenvk_uninit(ctx); 113 | + return false; 114 | +} 115 | + 116 | +static bool moltenvk_reconfig(struct ra_ctx *ctx) 117 | +{ 118 | + struct priv *p = ctx->priv; 119 | + CGSize s = p->layer.drawableSize; 120 | + ra_vk_ctx_resize(ctx, s.width, s.height); 121 | + return true; 122 | +} 123 | + 124 | +static int moltenvk_control(struct ra_ctx *ctx, int *events, int request, void *arg) 125 | +{ 126 | + return VO_NOTIMPL; 127 | +} 128 | + 129 | +const struct ra_ctx_fns ra_ctx_vulkan_moltenvk = { 130 | + .type = "vulkan", 131 | + .name = "moltenvk", 132 | + .reconfig = moltenvk_reconfig, 133 | + .control = moltenvk_control, 134 | + .init = moltenvk_init, 135 | + .uninit = moltenvk_uninit, 136 | +}; 137 | diff --git forkSrcPrefix/video/out/vulkan/common.h forkDstPrefix/video/out/vulkan/common.h 138 | index e75cb228f8d99462ccecf7780098ea97ae7cfe02..afc17284773204563f4c90b4860758e61068d460 100644 139 | --- forkSrcPrefix/video/out/vulkan/common.h 140 | +++ forkDstPrefix/video/out/vulkan/common.h 141 | @@ -22,6 +22,9 @@ 142 | #if HAVE_WIN32_DESKTOP 143 | #define VK_USE_PLATFORM_WIN32_KHR 144 | #endif 145 | +#if HAVE_MOLTENVK 146 | +#include 147 | +#endif 148 | #if HAVE_COCOA 149 | #define VK_USE_PLATFORM_METAL_EXT 150 | #endif 151 | diff --git forkSrcPrefix/video/out/gpu/context.c forkDstPrefix/video/out/gpu/context.c 152 | index 75dd804005ba3a1e36375b47dcc9d9bb756ab867..4ab72a00b3dd3539a06129702e41796fd00a8af3 100644 153 | --- forkSrcPrefix/video/out/gpu/context.c 154 | +++ forkDstPrefix/video/out/gpu/context.c 155 | @@ -50,6 +50,7 @@ extern const struct ra_ctx_fns ra_ctx_vulkan_xlib; 156 | extern const struct ra_ctx_fns ra_ctx_vulkan_android; 157 | extern const struct ra_ctx_fns ra_ctx_vulkan_display; 158 | extern const struct ra_ctx_fns ra_ctx_vulkan_mac; 159 | +extern const struct ra_ctx_fns ra_ctx_vulkan_moltenvk; 160 | 161 | /* Direct3D 11 */ 162 | extern const struct ra_ctx_fns ra_ctx_d3d11; 163 | @@ -126,6 +127,9 @@ static const struct ra_ctx_fns *contexts[] = { 164 | 165 | // Vulkan contexts (fallbacks): 166 | #if HAVE_VULKAN 167 | +#if HAVE_MOLTENVK 168 | + &ra_ctx_vulkan_moltenvk, 169 | +#endif 170 | #if HAVE_ANDROID 171 | &ra_ctx_vulkan_android, 172 | #endif 173 | -------------------------------------------------------------------------------- /Sources/_FFmpeg-GPL/dummy.c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mpvkit/MPVKit/32794687269115d5f93495d5bc3a86bf7bf95293/Sources/_FFmpeg-GPL/dummy.c -------------------------------------------------------------------------------- /Sources/_FFmpeg-GPL/include/dummy.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mpvkit/MPVKit/32794687269115d5f93495d5bc3a86bf7bf95293/Sources/_FFmpeg-GPL/include/dummy.h -------------------------------------------------------------------------------- /Sources/_FFmpeg/dummy.c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mpvkit/MPVKit/32794687269115d5f93495d5bc3a86bf7bf95293/Sources/_FFmpeg/dummy.c -------------------------------------------------------------------------------- /Sources/_FFmpeg/include/dummy.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mpvkit/MPVKit/32794687269115d5f93495d5bc3a86bf7bf95293/Sources/_FFmpeg/include/dummy.h -------------------------------------------------------------------------------- /Sources/_MPVKit-GPL/dummy.c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mpvkit/MPVKit/32794687269115d5f93495d5bc3a86bf7bf95293/Sources/_MPVKit-GPL/dummy.c -------------------------------------------------------------------------------- /Sources/_MPVKit-GPL/include/dummy.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mpvkit/MPVKit/32794687269115d5f93495d5bc3a86bf7bf95293/Sources/_MPVKit-GPL/include/dummy.h -------------------------------------------------------------------------------- /Sources/_MPVKit/dummy.c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mpvkit/MPVKit/32794687269115d5f93495d5bc3a86bf7bf95293/Sources/_MPVKit/dummy.c -------------------------------------------------------------------------------- /Sources/_MPVKit/include/dummy.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mpvkit/MPVKit/32794687269115d5f93495d5bc3a86bf7bf95293/Sources/_MPVKit/include/dummy.h -------------------------------------------------------------------------------- /docs/Package.template.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.8 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "MPVKit", 7 | platforms: [.macOS(.v10_15), .iOS(.v13), .tvOS(.v13)], 8 | products: [ 9 | .library( 10 | name: "MPVKit", 11 | targets: ["_MPVKit"] 12 | ), 13 | .library( 14 | name: "MPVKit-GPL", 15 | targets: ["_MPVKit-GPL"] 16 | ), 17 | ], 18 | targets: [ 19 | .target( 20 | name: "_MPVKit", 21 | dependencies: [ 22 | "Libmpv", "_FFmpeg", "Libuchardet", "Libbluray", 23 | .target(name: "Libluajit", condition: .when(platforms: [.macOS])), 24 | ], 25 | path: "Sources/_MPVKit", 26 | linkerSettings: [ 27 | .linkedFramework("AVFoundation"), 28 | .linkedFramework("CoreAudio"), 29 | ] 30 | ), 31 | .target( 32 | name: "_FFmpeg", 33 | dependencies: [ 34 | "Libavcodec", "Libavdevice", "Libavfilter", "Libavformat", "Libavutil", "Libswresample", "Libswscale", 35 | "Libssl", "Libcrypto", "Libass", "Libfreetype", "Libfribidi", "Libharfbuzz", 36 | "MoltenVK", "Libshaderc_combined", "lcms2", "Libplacebo", "Libdovi", "Libunibreak", 37 | "gmp", "nettle", "hogweed", "gnutls", "Libdav1d", "Libuavs3d" 38 | ], 39 | path: "Sources/_FFmpeg", 40 | linkerSettings: [ 41 | .linkedFramework("AudioToolbox"), 42 | .linkedFramework("CoreVideo"), 43 | .linkedFramework("CoreFoundation"), 44 | .linkedFramework("CoreMedia"), 45 | .linkedFramework("Metal"), 46 | .linkedFramework("VideoToolbox"), 47 | .linkedLibrary("bz2"), 48 | .linkedLibrary("iconv"), 49 | .linkedLibrary("expat"), 50 | .linkedLibrary("resolv"), 51 | .linkedLibrary("xml2"), 52 | .linkedLibrary("z"), 53 | .linkedLibrary("c++"), 54 | ] 55 | ), 56 | .target( 57 | name: "_MPVKit-GPL", 58 | dependencies: [ 59 | "Libmpv-GPL", "_FFmpeg-GPL", "Libuchardet", "Libbluray", 60 | .target(name: "Libluajit", condition: .when(platforms: [.macOS])), 61 | ], 62 | path: "Sources/_MPVKit-GPL", 63 | linkerSettings: [ 64 | .linkedFramework("AVFoundation"), 65 | .linkedFramework("CoreAudio"), 66 | ] 67 | ), 68 | .target( 69 | name: "_FFmpeg-GPL", 70 | dependencies: [ 71 | "Libavcodec-GPL", "Libavdevice-GPL", "Libavfilter-GPL", "Libavformat-GPL", "Libavutil-GPL", "Libswresample-GPL", "Libswscale-GPL", 72 | "Libssl", "Libcrypto", "Libass", "Libfreetype", "Libfribidi", "Libharfbuzz", 73 | "MoltenVK", "Libshaderc_combined", "lcms2", "Libplacebo", "Libdovi", "Libunibreak", 74 | "Libsmbclient", "gmp", "nettle", "hogweed", "gnutls", "Libdav1d", "Libuavs3d" 75 | ], 76 | path: "Sources/_FFmpeg-GPL", 77 | linkerSettings: [ 78 | .linkedFramework("AudioToolbox"), 79 | .linkedFramework("CoreVideo"), 80 | .linkedFramework("CoreFoundation"), 81 | .linkedFramework("CoreMedia"), 82 | .linkedFramework("Metal"), 83 | .linkedFramework("VideoToolbox"), 84 | .linkedLibrary("bz2"), 85 | .linkedLibrary("iconv"), 86 | .linkedLibrary("expat"), 87 | .linkedLibrary("resolv"), 88 | .linkedLibrary("xml2"), 89 | .linkedLibrary("z"), 90 | .linkedLibrary("c++"), 91 | ] 92 | ), 93 | 94 | .binaryTarget( 95 | name: "Libmpv-GPL", 96 | url: "\(Libmpv-GPL_url)", 97 | checksum: "\(Libmpv-GPL_checksum)" 98 | ), 99 | .binaryTarget( 100 | name: "Libavcodec-GPL", 101 | url: "\(Libavcodec-GPL_url)", 102 | checksum: "\(Libavcodec-GPL_checksum)" 103 | ), 104 | .binaryTarget( 105 | name: "Libavdevice-GPL", 106 | url: "\(Libavdevice-GPL_url)", 107 | checksum: "\(Libavdevice-GPL_checksum)" 108 | ), 109 | .binaryTarget( 110 | name: "Libavformat-GPL", 111 | url: "\(Libavformat-GPL_url)", 112 | checksum: "\(Libavformat-GPL_checksum)" 113 | ), 114 | .binaryTarget( 115 | name: "Libavfilter-GPL", 116 | url: "\(Libavfilter-GPL_url)", 117 | checksum: "\(Libavfilter-GPL_checksum)" 118 | ), 119 | .binaryTarget( 120 | name: "Libavutil-GPL", 121 | url: "\(Libavutil-GPL_url)", 122 | checksum: "\(Libavutil-GPL_checksum)" 123 | ), 124 | .binaryTarget( 125 | name: "Libswresample-GPL", 126 | url: "\(Libswresample-GPL_url)", 127 | checksum: "\(Libswresample-GPL_checksum)" 128 | ), 129 | .binaryTarget( 130 | name: "Libswscale-GPL", 131 | url: "\(Libswscale-GPL_url)", 132 | checksum: "\(Libswscale-GPL_checksum)" 133 | ), 134 | //AUTO_GENERATE_TARGETS_BEGIN// 135 | //AUTO_GENERATE_TARGETS_END// 136 | ] 137 | ) 138 | -------------------------------------------------------------------------------- /mpv.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | arch_info=$(uname -m) 4 | if [[ "$arch_info" == "i386" || "$arch_info" == "i686" || "$arch_info" == "x86_64" ]]; then 5 | arch="x86_64" 6 | else 7 | arch="arm64" 8 | fi 9 | 10 | bin="./dist/libmpv/macos/thin/$arch/bin/mpv" 11 | if [ ! -f "$bin" ]; then 12 | echo "mpv binary not found on path: $bin, please build first." 13 | exit 1 14 | fi 15 | 16 | $bin "$@" --------------------------------------------------------------------------------