├── .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 | [](https://github.com/mpv-player/mpv)
4 | [](https://github.com/FFmpeg/FFmpeg)
5 | [](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 | [](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 "$@"
--------------------------------------------------------------------------------