├── .gitattributes
├── .github
└── ISSUE_TEMPLATE
│ └── bug_report.md
├── .gitignore
├── CODE_OF_CONDUCT.md
├── LICENSE
├── Mpv.NET.WPFExample LICENSE
├── Mpv.NET.WinFormsExample LICENSE
├── README.md
└── src
├── Mpv.NET.WPFExample
├── App.config
├── App.xaml
├── App.xaml.cs
├── MainWindow.xaml
├── MainWindow.xaml.cs
├── Mpv.NET.WPFExample.csproj
├── Properties
│ └── AssemblyInfo.cs
└── lib
│ ├── mpv-1.dll
│ ├── youtube-dl.exe
│ └── ytdl_hook.lua
├── Mpv.NET.WinFormsExample
├── App.config
├── MainForm.Designer.cs
├── MainForm.cs
├── MainForm.resx
├── Mpv.NET.WinFormsExample.csproj
├── Program.cs
├── Properties
│ └── AssemblyInfo.cs
└── lib
│ ├── mpv-1.dll
│ ├── youtube-dl.exe
│ └── ytdl_hook.lua
├── Mpv.NET.sln
└── Mpv.NET
├── API
├── Enums
│ ├── MpvEndFileReason.cs
│ ├── MpvError.cs
│ ├── MpvEventID.cs
│ ├── MpvFormat.cs
│ └── MpvLogLevel.cs
├── EventArgs
│ ├── MpvClientMessageEventArgs.cs
│ ├── MpvCommandReplyEventArgs.cs
│ ├── MpvEndFileEventArgs.cs
│ ├── MpvGetPropertyReplyEventArgs.cs
│ ├── MpvLogMessageEventArgs.cs
│ ├── MpvPropertyChangeEventArgs.cs
│ └── MpvSetPropertyReplyEventArgs.cs
├── Exceptions
│ └── MpvAPIException.cs
├── Helpers
│ └── MpvLogLevelHelper.cs
├── IMpvEventLoop.cs
├── IMpvFunctions.cs
├── Interop
│ ├── Mpv
│ │ ├── MpvMarshal.cs
│ │ └── MpvStringMarshaler.cs
│ └── Windows
│ │ └── WinFunctions.cs
├── Mpv.cs
├── MpvEventLoop.cs
├── MpvEvents.cs
├── MpvFunctionDeclarations.cs
├── MpvFunctions.cs
└── Structs
│ ├── MpvEvent.cs
│ ├── MpvEventClientMessage.cs
│ ├── MpvEventEndFile.cs
│ ├── MpvEventProperty.cs
│ └── MpvLogMessage.cs
├── Guard.cs
├── Mpv.NET.csproj
└── Player
├── EventArgs.cs
├── IMpvPlayer.cs
├── KeepOpen
├── KeepOpen.cs
└── KeepOpenHelper.cs
├── LoadMethod
├── LoadMethod.cs
└── LoadMethodHelper.cs
├── MpvPlayer.cs
├── MpvPlayerException.cs
├── MpvPlayerHelper.cs
├── WinFunctions.cs
└── YouTubeDlQuality
├── YouTubeDlQuality.cs
└── YouTubeDlQualityHelper.cs
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 |
6 | ###############################################################################
7 | # Set default behavior for command prompt diff.
8 | #
9 | # This is need for earlier builds of msysgit that does not have it on by
10 | # default for csharp files.
11 | # Note: This is only used by command line
12 | ###############################################################################
13 | #*.cs diff=csharp
14 |
15 | ###############################################################################
16 | # Set the merge driver for project and solution files
17 | #
18 | # Merging from the command prompt will add diff markers to the files if there
19 | # are conflicts (Merging from VS is not affected by the settings below, in VS
20 | # the diff markers are never inserted). Diff markers may cause the following
21 | # file extensions to fail to load in VS. An alternative would be to treat
22 | # these files as binary and thus will always conflict and require user
23 | # intervention with every merge. To do so, just uncomment the entries below
24 | ###############################################################################
25 | #*.sln merge=binary
26 | #*.csproj merge=binary
27 | #*.vbproj merge=binary
28 | #*.vcxproj merge=binary
29 | #*.vcproj merge=binary
30 | #*.dbproj merge=binary
31 | #*.fsproj merge=binary
32 | #*.lsproj merge=binary
33 | #*.wixproj merge=binary
34 | #*.modelproj merge=binary
35 | #*.sqlproj merge=binary
36 | #*.wwaproj merge=binary
37 |
38 | ###############################################################################
39 | # behavior for image files
40 | #
41 | # image files are treated as binary by default.
42 | ###############################################################################
43 | #*.jpg binary
44 | #*.png binary
45 | #*.gif binary
46 |
47 | ###############################################################################
48 | # diff behavior for common document formats
49 | #
50 | # Convert binary document formats to text before diffing them. This feature
51 | # is only available from the command line. Turn it on by uncommenting the
52 | # entries below.
53 | ###############################################################################
54 | #*.doc diff=astextplain
55 | #*.DOC diff=astextplain
56 | #*.docx diff=astextplain
57 | #*.DOCX diff=astextplain
58 | #*.dot diff=astextplain
59 | #*.DOT diff=astextplain
60 | #*.pdf diff=astextplain
61 | #*.PDF diff=astextplain
62 | #*.rtf diff=astextplain
63 | #*.RTF diff=astextplain
64 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Mpv.NET (lib) Version**
11 | The version of Mpv.NET (lib) you are using when encountering the problem.
12 |
13 | **Describe the bug**
14 | A clear and concise description of what the bug is.
15 |
16 | **To Reproduce**
17 | Steps to reproduce the behavior:
18 | 1. Go to '...'
19 | 2. Click on '....'
20 | 3. Scroll down to '....'
21 | 4. See error
22 |
23 | **Sample Repository**
24 | If applicable, create and link a sample repository where the problem can be reproduced.
25 |
26 | **Expected behavior**
27 | A clear and concise description of what you expected to happen.
28 |
29 | **Screenshots**
30 | If applicable, add screenshots to help explain your problem.
31 |
32 | **Additional context**
33 | Add any other context about the problem here.
34 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # User-specific files
5 | *.suo
6 | *.user
7 | *.userosscache
8 | *.sln.docstates
9 |
10 | # User-specific files (MonoDevelop/Xamarin Studio)
11 | *.userprefs
12 |
13 | # Build results
14 | [Dd]ebug/
15 | [Dd]ebugPublic/
16 | [Rr]elease/
17 | [Rr]eleases/
18 | x64/
19 | x86/
20 | bld/
21 | [Bb]in/
22 | [Oo]bj/
23 | [Ll]og/
24 |
25 | # Visual Studio 2015 cache/options directory
26 | .vs/
27 | # Uncomment if you have tasks that create the project's static files in wwwroot
28 | #wwwroot/
29 |
30 | # MSTest test Results
31 | [Tt]est[Rr]esult*/
32 | [Bb]uild[Ll]og.*
33 |
34 | # NUNIT
35 | *.VisualState.xml
36 | TestResult.xml
37 |
38 | # Build Results of an ATL Project
39 | [Dd]ebugPS/
40 | [Rr]eleasePS/
41 | dlldata.c
42 |
43 | # DNX
44 | project.lock.json
45 | project.fragment.lock.json
46 | artifacts/
47 |
48 | *_i.c
49 | *_p.c
50 | *_i.h
51 | *.ilk
52 | *.meta
53 | *.obj
54 | *.pch
55 | *.pdb
56 | *.pgc
57 | *.pgd
58 | *.rsp
59 | *.sbr
60 | *.tlb
61 | *.tli
62 | *.tlh
63 | *.tmp
64 | *.tmp_proj
65 | *.log
66 | *.vspscc
67 | *.vssscc
68 | .builds
69 | *.pidb
70 | *.svclog
71 | *.scc
72 |
73 | # Chutzpah Test files
74 | _Chutzpah*
75 |
76 | # Visual C++ cache files
77 | ipch/
78 | *.aps
79 | *.ncb
80 | *.opendb
81 | *.opensdf
82 | *.sdf
83 | *.cachefile
84 | *.VC.db
85 | *.VC.VC.opendb
86 |
87 | # Visual Studio profiler
88 | *.psess
89 | *.vsp
90 | *.vspx
91 | *.sap
92 |
93 | # TFS 2012 Local Workspace
94 | $tf/
95 |
96 | # Guidance Automation Toolkit
97 | *.gpState
98 |
99 | # ReSharper is a .NET coding add-in
100 | _ReSharper*/
101 | *.[Rr]e[Ss]harper
102 | *.DotSettings.user
103 |
104 | # JustCode is a .NET coding add-in
105 | .JustCode
106 |
107 | # TeamCity is a build add-in
108 | _TeamCity*
109 |
110 | # DotCover is a Code Coverage Tool
111 | *.dotCover
112 |
113 | # NCrunch
114 | _NCrunch_*
115 | .*crunch*.local.xml
116 | nCrunchTemp_*
117 |
118 | # MightyMoose
119 | *.mm.*
120 | AutoTest.Net/
121 |
122 | # Web workbench (sass)
123 | .sass-cache/
124 |
125 | # Installshield output folder
126 | [Ee]xpress/
127 |
128 | # DocProject is a documentation generator add-in
129 | DocProject/buildhelp/
130 | DocProject/Help/*.HxT
131 | DocProject/Help/*.HxC
132 | DocProject/Help/*.hhc
133 | DocProject/Help/*.hhk
134 | DocProject/Help/*.hhp
135 | DocProject/Help/Html2
136 | DocProject/Help/html
137 |
138 | # Click-Once directory
139 | publish/
140 |
141 | # Publish Web Output
142 | *.[Pp]ublish.xml
143 | *.azurePubxml
144 | # TODO: Comment the next line if you want to checkin your web deploy settings
145 | # but database connection strings (with potential passwords) will be unencrypted
146 | #*.pubxml
147 | *.publishproj
148 |
149 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
150 | # checkin your Azure Web App publish settings, but sensitive information contained
151 | # in these scripts will be unencrypted
152 | PublishScripts/
153 |
154 | # NuGet Packages
155 | *.nupkg
156 | # The packages folder can be ignored because of Package Restore
157 | **/packages/*
158 | # except build/, which is used as an MSBuild target.
159 | !**/packages/build/
160 | # Uncomment if necessary however generally it will be regenerated when needed
161 | #!**/packages/repositories.config
162 | # NuGet v3's project.json files produces more ignoreable files
163 | *.nuget.props
164 | *.nuget.targets
165 |
166 | # Microsoft Azure Build Output
167 | csx/
168 | *.build.csdef
169 |
170 | # Microsoft Azure Emulator
171 | ecf/
172 | rcf/
173 |
174 | # Windows Store app package directories and files
175 | AppPackages/
176 | BundleArtifacts/
177 | Package.StoreAssociation.xml
178 | _pkginfo.txt
179 |
180 | # Visual Studio cache files
181 | # files ending in .cache can be ignored
182 | *.[Cc]ache
183 | # but keep track of directories ending in .cache
184 | !*.[Cc]ache/
185 |
186 | # Others
187 | ClientBin/
188 | ~$*
189 | *~
190 | *.dbmdl
191 | *.dbproj.schemaview
192 | *.jfm
193 | *.pfx
194 | *.publishsettings
195 | node_modules/
196 | orleans.codegen.cs
197 |
198 | # Since there are multiple workflows, uncomment next line to ignore bower_components
199 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
200 | #bower_components/
201 |
202 | # RIA/Silverlight projects
203 | Generated_Code/
204 |
205 | # Backup & report files from converting an old project file
206 | # to a newer Visual Studio version. Backup files are not needed,
207 | # because we have git ;-)
208 | _UpgradeReport_Files/
209 | Backup*/
210 | UpgradeLog*.XML
211 | UpgradeLog*.htm
212 |
213 | # SQL Server files
214 | *.mdf
215 | *.ldf
216 |
217 | # Business Intelligence projects
218 | *.rdl.data
219 | *.bim.layout
220 | *.bim_*.settings
221 |
222 | # Microsoft Fakes
223 | FakesAssemblies/
224 |
225 | # GhostDoc plugin setting file
226 | *.GhostDoc.xml
227 |
228 | # Node.js Tools for Visual Studio
229 | .ntvs_analysis.dat
230 |
231 | # Visual Studio 6 build log
232 | *.plg
233 |
234 | # Visual Studio 6 workspace options file
235 | *.opt
236 |
237 | # Visual Studio LightSwitch build output
238 | **/*.HTMLClient/GeneratedArtifacts
239 | **/*.DesktopClient/GeneratedArtifacts
240 | **/*.DesktopClient/ModelManifest.xml
241 | **/*.Server/GeneratedArtifacts
242 | **/*.Server/ModelManifest.xml
243 | _Pvt_Extensions
244 |
245 | # Paket dependency manager
246 | .paket/paket.exe
247 | paket-files/
248 |
249 | # FAKE - F# Make
250 | .fake/
251 |
252 | # JetBrains Rider
253 | .idea/
254 | *.sln.iml
255 |
256 | # CodeRush
257 | .cr/
258 |
259 | # Python Tools for Visual Studio (PTVS)
260 | __pycache__/
261 | *.pyc
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, gender identity and expression, level of experience,
9 | education, socio-economic status, nationality, personal appearance, race,
10 | religion, or sexual identity and orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | * The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at hudec117@gmail.com. All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
72 |
73 | [homepage]: https://www.contributor-covenant.org
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Aurel Hudec Jr
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/Mpv.NET.WPFExample LICENSE:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 2, June 1991
3 |
4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
6 | Everyone is permitted to copy and distribute verbatim copies
7 | of this license document, but changing it is not allowed.
8 |
9 | Preamble
10 |
11 | The licenses for most software are designed to take away your
12 | freedom to share and change it. By contrast, the GNU General Public
13 | License is intended to guarantee your freedom to share and change free
14 | software--to make sure the software is free for all its users. This
15 | General Public License applies to most of the Free Software
16 | Foundation's software and to any other program whose authors commit to
17 | using it. (Some other Free Software Foundation software is covered by
18 | the GNU Lesser General Public License instead.) You can apply it to
19 | your programs, too.
20 |
21 | When we speak of free software, we are referring to freedom, not
22 | price. Our General Public Licenses are designed to make sure that you
23 | have the freedom to distribute copies of free software (and charge for
24 | this service if you wish), that you receive source code or can get it
25 | if you want it, that you can change the software or use pieces of it
26 | in new free programs; and that you know you can do these things.
27 |
28 | To protect your rights, we need to make restrictions that forbid
29 | anyone to deny you these rights or to ask you to surrender the rights.
30 | These restrictions translate to certain responsibilities for you if you
31 | distribute copies of the software, or if you modify it.
32 |
33 | For example, if you distribute copies of such a program, whether
34 | gratis or for a fee, you must give the recipients all the rights that
35 | you have. You must make sure that they, too, receive or can get the
36 | source code. And you must show them these terms so they know their
37 | rights.
38 |
39 | We protect your rights with two steps: (1) copyright the software, and
40 | (2) offer you this license which gives you legal permission to copy,
41 | distribute and/or modify the software.
42 |
43 | Also, for each author's protection and ours, we want to make certain
44 | that everyone understands that there is no warranty for this free
45 | software. If the software is modified by someone else and passed on, we
46 | want its recipients to know that what they have is not the original, so
47 | that any problems introduced by others will not reflect on the original
48 | authors' reputations.
49 |
50 | Finally, any free program is threatened constantly by software
51 | patents. We wish to avoid the danger that redistributors of a free
52 | program will individually obtain patent licenses, in effect making the
53 | program proprietary. To prevent this, we have made it clear that any
54 | patent must be licensed for everyone's free use or not licensed at all.
55 |
56 | The precise terms and conditions for copying, distribution and
57 | modification follow.
58 |
59 | GNU GENERAL PUBLIC LICENSE
60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
61 |
62 | 0. This License applies to any program or other work which contains
63 | a notice placed by the copyright holder saying it may be distributed
64 | under the terms of this General Public License. The "Program", below,
65 | refers to any such program or work, and a "work based on the Program"
66 | means either the Program or any derivative work under copyright law:
67 | that is to say, a work containing the Program or a portion of it,
68 | either verbatim or with modifications and/or translated into another
69 | language. (Hereinafter, translation is included without limitation in
70 | the term "modification".) Each licensee is addressed as "you".
71 |
72 | Activities other than copying, distribution and modification are not
73 | covered by this License; they are outside its scope. The act of
74 | running the Program is not restricted, and the output from the Program
75 | is covered only if its contents constitute a work based on the
76 | Program (independent of having been made by running the Program).
77 | Whether that is true depends on what the Program does.
78 |
79 | 1. You may copy and distribute verbatim copies of the Program's
80 | source code as you receive it, in any medium, provided that you
81 | conspicuously and appropriately publish on each copy an appropriate
82 | copyright notice and disclaimer of warranty; keep intact all the
83 | notices that refer to this License and to the absence of any warranty;
84 | and give any other recipients of the Program a copy of this License
85 | along with the Program.
86 |
87 | You may charge a fee for the physical act of transferring a copy, and
88 | you may at your option offer warranty protection in exchange for a fee.
89 |
90 | 2. You may modify your copy or copies of the Program or any portion
91 | of it, thus forming a work based on the Program, and copy and
92 | distribute such modifications or work under the terms of Section 1
93 | above, provided that you also meet all of these conditions:
94 |
95 | a) You must cause the modified files to carry prominent notices
96 | stating that you changed the files and the date of any change.
97 |
98 | b) You must cause any work that you distribute or publish, that in
99 | whole or in part contains or is derived from the Program or any
100 | part thereof, to be licensed as a whole at no charge to all third
101 | parties under the terms of this License.
102 |
103 | c) If the modified program normally reads commands interactively
104 | when run, you must cause it, when started running for such
105 | interactive use in the most ordinary way, to print or display an
106 | announcement including an appropriate copyright notice and a
107 | notice that there is no warranty (or else, saying that you provide
108 | a warranty) and that users may redistribute the program under
109 | these conditions, and telling the user how to view a copy of this
110 | License. (Exception: if the Program itself is interactive but
111 | does not normally print such an announcement, your work based on
112 | the Program is not required to print an announcement.)
113 |
114 | These requirements apply to the modified work as a whole. If
115 | identifiable sections of that work are not derived from the Program,
116 | and can be reasonably considered independent and separate works in
117 | themselves, then this License, and its terms, do not apply to those
118 | sections when you distribute them as separate works. But when you
119 | distribute the same sections as part of a whole which is a work based
120 | on the Program, the distribution of the whole must be on the terms of
121 | this License, whose permissions for other licensees extend to the
122 | entire whole, and thus to each and every part regardless of who wrote it.
123 |
124 | Thus, it is not the intent of this section to claim rights or contest
125 | your rights to work written entirely by you; rather, the intent is to
126 | exercise the right to control the distribution of derivative or
127 | collective works based on the Program.
128 |
129 | In addition, mere aggregation of another work not based on the Program
130 | with the Program (or with a work based on the Program) on a volume of
131 | a storage or distribution medium does not bring the other work under
132 | the scope of this License.
133 |
134 | 3. You may copy and distribute the Program (or a work based on it,
135 | under Section 2) in object code or executable form under the terms of
136 | Sections 1 and 2 above provided that you also do one of the following:
137 |
138 | a) Accompany it with the complete corresponding machine-readable
139 | source code, which must be distributed under the terms of Sections
140 | 1 and 2 above on a medium customarily used for software interchange; or,
141 |
142 | b) Accompany it with a written offer, valid for at least three
143 | years, to give any third party, for a charge no more than your
144 | cost of physically performing source distribution, a complete
145 | machine-readable copy of the corresponding source code, to be
146 | distributed under the terms of Sections 1 and 2 above on a medium
147 | customarily used for software interchange; or,
148 |
149 | c) Accompany it with the information you received as to the offer
150 | to distribute corresponding source code. (This alternative is
151 | allowed only for noncommercial distribution and only if you
152 | received the program in object code or executable form with such
153 | an offer, in accord with Subsection b above.)
154 |
155 | The source code for a work means the preferred form of the work for
156 | making modifications to it. For an executable work, complete source
157 | code means all the source code for all modules it contains, plus any
158 | associated interface definition files, plus the scripts used to
159 | control compilation and installation of the executable. However, as a
160 | special exception, the source code distributed need not include
161 | anything that is normally distributed (in either source or binary
162 | form) with the major components (compiler, kernel, and so on) of the
163 | operating system on which the executable runs, unless that component
164 | itself accompanies the executable.
165 |
166 | If distribution of executable or object code is made by offering
167 | access to copy from a designated place, then offering equivalent
168 | access to copy the source code from the same place counts as
169 | distribution of the source code, even though third parties are not
170 | compelled to copy the source along with the object code.
171 |
172 | 4. You may not copy, modify, sublicense, or distribute the Program
173 | except as expressly provided under this License. Any attempt
174 | otherwise to copy, modify, sublicense or distribute the Program is
175 | void, and will automatically terminate your rights under this License.
176 | However, parties who have received copies, or rights, from you under
177 | this License will not have their licenses terminated so long as such
178 | parties remain in full compliance.
179 |
180 | 5. You are not required to accept this License, since you have not
181 | signed it. However, nothing else grants you permission to modify or
182 | distribute the Program or its derivative works. These actions are
183 | prohibited by law if you do not accept this License. Therefore, by
184 | modifying or distributing the Program (or any work based on the
185 | Program), you indicate your acceptance of this License to do so, and
186 | all its terms and conditions for copying, distributing or modifying
187 | the Program or works based on it.
188 |
189 | 6. Each time you redistribute the Program (or any work based on the
190 | Program), the recipient automatically receives a license from the
191 | original licensor to copy, distribute or modify the Program subject to
192 | these terms and conditions. You may not impose any further
193 | restrictions on the recipients' exercise of the rights granted herein.
194 | You are not responsible for enforcing compliance by third parties to
195 | this License.
196 |
197 | 7. If, as a consequence of a court judgment or allegation of patent
198 | infringement or for any other reason (not limited to patent issues),
199 | conditions are imposed on you (whether by court order, agreement or
200 | otherwise) that contradict the conditions of this License, they do not
201 | excuse you from the conditions of this License. If you cannot
202 | distribute so as to satisfy simultaneously your obligations under this
203 | License and any other pertinent obligations, then as a consequence you
204 | may not distribute the Program at all. For example, if a patent
205 | license would not permit royalty-free redistribution of the Program by
206 | all those who receive copies directly or indirectly through you, then
207 | the only way you could satisfy both it and this License would be to
208 | refrain entirely from distribution of the Program.
209 |
210 | If any portion of this section is held invalid or unenforceable under
211 | any particular circumstance, the balance of the section is intended to
212 | apply and the section as a whole is intended to apply in other
213 | circumstances.
214 |
215 | It is not the purpose of this section to induce you to infringe any
216 | patents or other property right claims or to contest validity of any
217 | such claims; this section has the sole purpose of protecting the
218 | integrity of the free software distribution system, which is
219 | implemented by public license practices. Many people have made
220 | generous contributions to the wide range of software distributed
221 | through that system in reliance on consistent application of that
222 | system; it is up to the author/donor to decide if he or she is willing
223 | to distribute software through any other system and a licensee cannot
224 | impose that choice.
225 |
226 | This section is intended to make thoroughly clear what is believed to
227 | be a consequence of the rest of this License.
228 |
229 | 8. If the distribution and/or use of the Program is restricted in
230 | certain countries either by patents or by copyrighted interfaces, the
231 | original copyright holder who places the Program under this License
232 | may add an explicit geographical distribution limitation excluding
233 | those countries, so that distribution is permitted only in or among
234 | countries not thus excluded. In such case, this License incorporates
235 | the limitation as if written in the body of this License.
236 |
237 | 9. The Free Software Foundation may publish revised and/or new versions
238 | of the General Public License from time to time. Such new versions will
239 | be similar in spirit to the present version, but may differ in detail to
240 | address new problems or concerns.
241 |
242 | Each version is given a distinguishing version number. If the Program
243 | specifies a version number of this License which applies to it and "any
244 | later version", you have the option of following the terms and conditions
245 | either of that version or of any later version published by the Free
246 | Software Foundation. If the Program does not specify a version number of
247 | this License, you may choose any version ever published by the Free Software
248 | Foundation.
249 |
250 | 10. If you wish to incorporate parts of the Program into other free
251 | programs whose distribution conditions are different, write to the author
252 | to ask for permission. For software which is copyrighted by the Free
253 | Software Foundation, write to the Free Software Foundation; we sometimes
254 | make exceptions for this. Our decision will be guided by the two goals
255 | of preserving the free status of all derivatives of our free software and
256 | of promoting the sharing and reuse of software generally.
257 |
258 | NO WARRANTY
259 |
260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
268 | REPAIR OR CORRECTION.
269 |
270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
278 | POSSIBILITY OF SUCH DAMAGES.
279 |
280 | END OF TERMS AND CONDITIONS
--------------------------------------------------------------------------------
/Mpv.NET.WinFormsExample LICENSE:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 2, June 1991
3 |
4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
6 | Everyone is permitted to copy and distribute verbatim copies
7 | of this license document, but changing it is not allowed.
8 |
9 | Preamble
10 |
11 | The licenses for most software are designed to take away your
12 | freedom to share and change it. By contrast, the GNU General Public
13 | License is intended to guarantee your freedom to share and change free
14 | software--to make sure the software is free for all its users. This
15 | General Public License applies to most of the Free Software
16 | Foundation's software and to any other program whose authors commit to
17 | using it. (Some other Free Software Foundation software is covered by
18 | the GNU Lesser General Public License instead.) You can apply it to
19 | your programs, too.
20 |
21 | When we speak of free software, we are referring to freedom, not
22 | price. Our General Public Licenses are designed to make sure that you
23 | have the freedom to distribute copies of free software (and charge for
24 | this service if you wish), that you receive source code or can get it
25 | if you want it, that you can change the software or use pieces of it
26 | in new free programs; and that you know you can do these things.
27 |
28 | To protect your rights, we need to make restrictions that forbid
29 | anyone to deny you these rights or to ask you to surrender the rights.
30 | These restrictions translate to certain responsibilities for you if you
31 | distribute copies of the software, or if you modify it.
32 |
33 | For example, if you distribute copies of such a program, whether
34 | gratis or for a fee, you must give the recipients all the rights that
35 | you have. You must make sure that they, too, receive or can get the
36 | source code. And you must show them these terms so they know their
37 | rights.
38 |
39 | We protect your rights with two steps: (1) copyright the software, and
40 | (2) offer you this license which gives you legal permission to copy,
41 | distribute and/or modify the software.
42 |
43 | Also, for each author's protection and ours, we want to make certain
44 | that everyone understands that there is no warranty for this free
45 | software. If the software is modified by someone else and passed on, we
46 | want its recipients to know that what they have is not the original, so
47 | that any problems introduced by others will not reflect on the original
48 | authors' reputations.
49 |
50 | Finally, any free program is threatened constantly by software
51 | patents. We wish to avoid the danger that redistributors of a free
52 | program will individually obtain patent licenses, in effect making the
53 | program proprietary. To prevent this, we have made it clear that any
54 | patent must be licensed for everyone's free use or not licensed at all.
55 |
56 | The precise terms and conditions for copying, distribution and
57 | modification follow.
58 |
59 | GNU GENERAL PUBLIC LICENSE
60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
61 |
62 | 0. This License applies to any program or other work which contains
63 | a notice placed by the copyright holder saying it may be distributed
64 | under the terms of this General Public License. The "Program", below,
65 | refers to any such program or work, and a "work based on the Program"
66 | means either the Program or any derivative work under copyright law:
67 | that is to say, a work containing the Program or a portion of it,
68 | either verbatim or with modifications and/or translated into another
69 | language. (Hereinafter, translation is included without limitation in
70 | the term "modification".) Each licensee is addressed as "you".
71 |
72 | Activities other than copying, distribution and modification are not
73 | covered by this License; they are outside its scope. The act of
74 | running the Program is not restricted, and the output from the Program
75 | is covered only if its contents constitute a work based on the
76 | Program (independent of having been made by running the Program).
77 | Whether that is true depends on what the Program does.
78 |
79 | 1. You may copy and distribute verbatim copies of the Program's
80 | source code as you receive it, in any medium, provided that you
81 | conspicuously and appropriately publish on each copy an appropriate
82 | copyright notice and disclaimer of warranty; keep intact all the
83 | notices that refer to this License and to the absence of any warranty;
84 | and give any other recipients of the Program a copy of this License
85 | along with the Program.
86 |
87 | You may charge a fee for the physical act of transferring a copy, and
88 | you may at your option offer warranty protection in exchange for a fee.
89 |
90 | 2. You may modify your copy or copies of the Program or any portion
91 | of it, thus forming a work based on the Program, and copy and
92 | distribute such modifications or work under the terms of Section 1
93 | above, provided that you also meet all of these conditions:
94 |
95 | a) You must cause the modified files to carry prominent notices
96 | stating that you changed the files and the date of any change.
97 |
98 | b) You must cause any work that you distribute or publish, that in
99 | whole or in part contains or is derived from the Program or any
100 | part thereof, to be licensed as a whole at no charge to all third
101 | parties under the terms of this License.
102 |
103 | c) If the modified program normally reads commands interactively
104 | when run, you must cause it, when started running for such
105 | interactive use in the most ordinary way, to print or display an
106 | announcement including an appropriate copyright notice and a
107 | notice that there is no warranty (or else, saying that you provide
108 | a warranty) and that users may redistribute the program under
109 | these conditions, and telling the user how to view a copy of this
110 | License. (Exception: if the Program itself is interactive but
111 | does not normally print such an announcement, your work based on
112 | the Program is not required to print an announcement.)
113 |
114 | These requirements apply to the modified work as a whole. If
115 | identifiable sections of that work are not derived from the Program,
116 | and can be reasonably considered independent and separate works in
117 | themselves, then this License, and its terms, do not apply to those
118 | sections when you distribute them as separate works. But when you
119 | distribute the same sections as part of a whole which is a work based
120 | on the Program, the distribution of the whole must be on the terms of
121 | this License, whose permissions for other licensees extend to the
122 | entire whole, and thus to each and every part regardless of who wrote it.
123 |
124 | Thus, it is not the intent of this section to claim rights or contest
125 | your rights to work written entirely by you; rather, the intent is to
126 | exercise the right to control the distribution of derivative or
127 | collective works based on the Program.
128 |
129 | In addition, mere aggregation of another work not based on the Program
130 | with the Program (or with a work based on the Program) on a volume of
131 | a storage or distribution medium does not bring the other work under
132 | the scope of this License.
133 |
134 | 3. You may copy and distribute the Program (or a work based on it,
135 | under Section 2) in object code or executable form under the terms of
136 | Sections 1 and 2 above provided that you also do one of the following:
137 |
138 | a) Accompany it with the complete corresponding machine-readable
139 | source code, which must be distributed under the terms of Sections
140 | 1 and 2 above on a medium customarily used for software interchange; or,
141 |
142 | b) Accompany it with a written offer, valid for at least three
143 | years, to give any third party, for a charge no more than your
144 | cost of physically performing source distribution, a complete
145 | machine-readable copy of the corresponding source code, to be
146 | distributed under the terms of Sections 1 and 2 above on a medium
147 | customarily used for software interchange; or,
148 |
149 | c) Accompany it with the information you received as to the offer
150 | to distribute corresponding source code. (This alternative is
151 | allowed only for noncommercial distribution and only if you
152 | received the program in object code or executable form with such
153 | an offer, in accord with Subsection b above.)
154 |
155 | The source code for a work means the preferred form of the work for
156 | making modifications to it. For an executable work, complete source
157 | code means all the source code for all modules it contains, plus any
158 | associated interface definition files, plus the scripts used to
159 | control compilation and installation of the executable. However, as a
160 | special exception, the source code distributed need not include
161 | anything that is normally distributed (in either source or binary
162 | form) with the major components (compiler, kernel, and so on) of the
163 | operating system on which the executable runs, unless that component
164 | itself accompanies the executable.
165 |
166 | If distribution of executable or object code is made by offering
167 | access to copy from a designated place, then offering equivalent
168 | access to copy the source code from the same place counts as
169 | distribution of the source code, even though third parties are not
170 | compelled to copy the source along with the object code.
171 |
172 | 4. You may not copy, modify, sublicense, or distribute the Program
173 | except as expressly provided under this License. Any attempt
174 | otherwise to copy, modify, sublicense or distribute the Program is
175 | void, and will automatically terminate your rights under this License.
176 | However, parties who have received copies, or rights, from you under
177 | this License will not have their licenses terminated so long as such
178 | parties remain in full compliance.
179 |
180 | 5. You are not required to accept this License, since you have not
181 | signed it. However, nothing else grants you permission to modify or
182 | distribute the Program or its derivative works. These actions are
183 | prohibited by law if you do not accept this License. Therefore, by
184 | modifying or distributing the Program (or any work based on the
185 | Program), you indicate your acceptance of this License to do so, and
186 | all its terms and conditions for copying, distributing or modifying
187 | the Program or works based on it.
188 |
189 | 6. Each time you redistribute the Program (or any work based on the
190 | Program), the recipient automatically receives a license from the
191 | original licensor to copy, distribute or modify the Program subject to
192 | these terms and conditions. You may not impose any further
193 | restrictions on the recipients' exercise of the rights granted herein.
194 | You are not responsible for enforcing compliance by third parties to
195 | this License.
196 |
197 | 7. If, as a consequence of a court judgment or allegation of patent
198 | infringement or for any other reason (not limited to patent issues),
199 | conditions are imposed on you (whether by court order, agreement or
200 | otherwise) that contradict the conditions of this License, they do not
201 | excuse you from the conditions of this License. If you cannot
202 | distribute so as to satisfy simultaneously your obligations under this
203 | License and any other pertinent obligations, then as a consequence you
204 | may not distribute the Program at all. For example, if a patent
205 | license would not permit royalty-free redistribution of the Program by
206 | all those who receive copies directly or indirectly through you, then
207 | the only way you could satisfy both it and this License would be to
208 | refrain entirely from distribution of the Program.
209 |
210 | If any portion of this section is held invalid or unenforceable under
211 | any particular circumstance, the balance of the section is intended to
212 | apply and the section as a whole is intended to apply in other
213 | circumstances.
214 |
215 | It is not the purpose of this section to induce you to infringe any
216 | patents or other property right claims or to contest validity of any
217 | such claims; this section has the sole purpose of protecting the
218 | integrity of the free software distribution system, which is
219 | implemented by public license practices. Many people have made
220 | generous contributions to the wide range of software distributed
221 | through that system in reliance on consistent application of that
222 | system; it is up to the author/donor to decide if he or she is willing
223 | to distribute software through any other system and a licensee cannot
224 | impose that choice.
225 |
226 | This section is intended to make thoroughly clear what is believed to
227 | be a consequence of the rest of this License.
228 |
229 | 8. If the distribution and/or use of the Program is restricted in
230 | certain countries either by patents or by copyrighted interfaces, the
231 | original copyright holder who places the Program under this License
232 | may add an explicit geographical distribution limitation excluding
233 | those countries, so that distribution is permitted only in or among
234 | countries not thus excluded. In such case, this License incorporates
235 | the limitation as if written in the body of this License.
236 |
237 | 9. The Free Software Foundation may publish revised and/or new versions
238 | of the General Public License from time to time. Such new versions will
239 | be similar in spirit to the present version, but may differ in detail to
240 | address new problems or concerns.
241 |
242 | Each version is given a distinguishing version number. If the Program
243 | specifies a version number of this License which applies to it and "any
244 | later version", you have the option of following the terms and conditions
245 | either of that version or of any later version published by the Free
246 | Software Foundation. If the Program does not specify a version number of
247 | this License, you may choose any version ever published by the Free Software
248 | Foundation.
249 |
250 | 10. If you wish to incorporate parts of the Program into other free
251 | programs whose distribution conditions are different, write to the author
252 | to ask for permission. For software which is copyrighted by the Free
253 | Software Foundation, write to the Free Software Foundation; we sometimes
254 | make exceptions for this. Our decision will be guided by the two goals
255 | of preserving the free status of all derivatives of our free software and
256 | of promoting the sharing and reuse of software generally.
257 |
258 | NO WARRANTY
259 |
260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
268 | REPAIR OR CORRECTION.
269 |
270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
278 | POSSIBILITY OF SUCH DAMAGES.
279 |
280 | END OF TERMS AND CONDITIONS
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Mpv.NET (lib)
2 |
3 | [](https://www.nuget.org/packages/Mpv.NET/)
4 | [](https://www.nuget.org/packages/Mpv.NET/)
5 |
6 | >Hello everyone!
7 |
8 | >I developed this library a few years ago for another project of mine. Ever since I completed that project I have not been able to maintain this repository and investigate the issues. I know that a lot of you find this library useful and I want it to continue but I am not able to dedicate time towards it any more.
9 |
10 | >If anyone is interested in maintaining this repository, reviewing pull requests and investigating issues. I would be more than happy to give you the right permissions to do that. I will also happily publish new version of the package on Nuget to make sure everyone can continue to enjoy the updates.
11 |
12 | .NET embeddable video/media player based on [mpv](https://github.com/mpv-player/mpv) for WinForms and WPF
13 |
14 | #### Player Features
15 |
16 | * Looping
17 | * Auto-play
18 | * Frame stepping
19 | * Asynchronous seeking
20 | * Change playback speed
21 | * Simple setup and usage
22 | * Logging from mpv
23 | * Add separate audio track
24 | * Playlist - Load, Next, Previous, Move, Remove or Clear
25 | * Optional [youtube-dl](https://rg3.github.io/youtube-dl/index.html) support to play videos from [hundreds of video sites](https://rg3.github.io/youtube-dl/supportedsites.html).
26 | * Change the desired video quality.
27 |
28 | #### Notes:
29 |
30 | * See [here](https://github.com/stax76/mpv.net) for Mpv.NET the C# media player based on mpv.
31 | * Very little documentation has been written for the C API wrapper. Consult [client.h](https://github.com/mpv-player/mpv/blob/master/libmpv/client.h).
32 | * The entirety of the mpv C API has not yet been implemented.
33 |
34 | If you encounter any bugs or would like to see a feature added then please open an issue. Contributions are very welcome!
35 |
36 | ## Download
37 |
38 | This package is available via [NuGet](https://www.nuget.org/packages/Mpv.NET).
39 |
40 | ## Prerequisites
41 |
42 | ### libmpv
43 |
44 | To use the API wrapper (and player) you will need libmpv.
45 |
46 | 1. Download libmpv from [here](https://sourceforge.net/projects/mpv-player-windows/files/libmpv/).
47 | * Either "i686" if your app is 32-bit or "x86_64" if your app is 64-bit
48 | 2. Extract "mpv-1.dll" from the archive into your project.
49 | (A "lib" folder in your project is common practice)
50 | 3. Include the DLL in your IDE and instruct your build system to copy the DLL to output.
51 | * In Visual Studio:
52 | 1. In Solution Explorer click the "Show All Files" button at the top. (make sure you have the correct project selected)
53 | 2. You should see the DLL show up, right click on it and select "Include In Project".
54 | 3. Right click on the DLL and select "Properties", then change the value for "Copy to Output Directory" to "Copy Always".
55 | 4. Done!
56 |
57 | If you wish to compile libmpv yourself, there is a [guide](https://github.com/mpv-player/mpv/blob/master/DOCS/compile-windows.md) available in the mpv repository.
58 |
59 | ### youtube-dl
60 |
61 | To enable youtube-dl functionality in the player you will need the youtube-dl executable and the ytdl hook script from mpv which allows mpv to communicate with youtube-dl.
62 |
63 | 1. Download the "youtube-dl.exe" executable from [here](https://rg3.github.io/youtube-dl/download.html).
64 | 2. Download the "ytdl_hook.lua" script from [here](https://github.com/mpv-player/mpv/blob/master/player/lua/ytdl_hook.lua).
65 | 3. Copy both files to your "lib" folder you made for libmpv.
66 | 4. Follow step #3 in the libmpv section but perform the steps on the "ytdl_hook.lua" script and "youtube-dl.exe" executable instead.
67 | 5. Near the start of the file, change the declaration of "ytdl" so it looks like this:
68 | ```lua
69 | local ytdl = {
70 | path = "lib\\youtube-dl.exe",
71 | searched = false,
72 | blacklisted = {}
73 | }
74 | ```
75 | 6. In your code, you will need to call the `EnableYouTubeDl` method on an instance of `MpvPlayer`.
76 | * If you placed your "ytdl_hook.lua" script somewhere other than the "lib" folder, you will need to pass the relative (to your apps executable) or absolute path of the script to `EnableYouTubeDl`.
77 | 7. Done!
78 |
79 | To keep youtube-dl functionality working:
80 | * Regularly update the "youtube-dl.exe" executable with latest version.
81 | * Regularly update the "ytdl_hook.lua" script with the latest version.
82 |
83 | If you have any doubts or questions regarding this process, please feel free to open an issue.
84 |
85 | ## Player
86 |
87 | This player was designed to work on Windows and tested in WPF and WinForms. Not tested on other platforms.
88 |
89 | To overlay controls over the top of the player please start with this [Stack Overflow post](https://stackoverflow.com/questions/5978917/render-wpf-control-on-top-of-windowsformshost).
90 |
91 | If you're looking for a media player UI, I'd recommend [MediaPlayerUI.NET](https://github.com/mysteryx93/MediaPlayerUI.NET). :)
92 |
93 | ### Initialisation
94 |
95 | `MpvPlayer` provides 4 constructors:
96 | 1. `MpvPlayer()`
97 | 2. `MpvPlayer(string libMpvPath)`
98 | 3. `MpvPlayer(IntPtr hwnd)`
99 | 4. `MpvPlayer(IntPtr hwnd, string libMpvPath)`
100 |
101 | Constructors #1 and #2 let mpv create it's own window and will not embed it.
102 |
103 | Constructors #3 and #4 allow you to specify a `hwnd` parameter which should be the handle to the host control where you want to embed the player.
104 |
105 | For the constructors where `libMpvPath` is not included, the player attempts to load libmpv from: (in order)
106 | * LibMpvPath property
107 | * "mpv-1.dll"
108 | * "lib\mpv-1.dll"
109 |
110 | ### WPF
111 |
112 | Since WPF doesn't keep traditional handles to controls we will need to use a `System.Windows.Forms.Control` object (E.g. `Panel` or nearly any other WinForms control) to host the mpv instance.
113 |
114 | First you will need to add a reference to `System.Windows.Forms` in your project:
115 | * In Visual Studio this can be achieved so:
116 | * Right click "References" in your project and click "Add Reference".
117 | * Navigate to Assemblies, find `System.Windows.Forms`, make sure it's ticked and click "OK".
118 |
119 | Next, you will need to reference the `System.Windows.Forms` namespace in XAML:
120 |
121 | ```xml
122 |
125 | ```
126 |
127 | Then create a `WindowsFormsHost` with a `Panel` from WinForms:
128 |
129 | ```xml
130 |
131 |
132 |
133 | ```
134 |
135 | If `WindowsFormHost` isn't found you will need to add a reference to `WindowsFormsIntegration` to your project:
136 | * Just as above: this can be achieved in Visual Studio by:
137 | * Right clicking "References" in your project and clicking "Add Reference".
138 | * Navigating to Assemblies, finding `WindowsFormsIntegration`, making sure it's ticked and clicking "OK".
139 |
140 | In your `.xaml.cs` file create a field/property of type `MpvPlayer` and initialise it using one of the constructors outlined above.
141 |
142 | In this example we called our `Panel` object `PlayerHost` so for the `hwnd` parameter in the constructor you would pass in `PlayerHost.Handle` like so:
143 | ```csharp
144 | player = new MpvPlayer(PlayerHost.Handle);
145 | ```
146 |
147 | See [Mpv.NET.WPFExample](https://github.com/hudec117/Mpv.NET/tree/master/src/Mpv.NET.WPFExample) project for a basic example.
148 |
149 | ### WinForms
150 |
151 | You can use any WinForms control, just pass the `Handle` property to the `MpvPlayer` constructor and you're done! Easy.
152 |
153 | See [Mpv.NET.WinFormsExample](https://github.com/hudec117/Mpv.NET/tree/master/src/Mpv.NET.WinFormsExample) project for a basic example.
154 |
155 | ### youtube-dl settings
156 |
157 | You have the option to set the desired quality of the media you're trying to play using youtube-dl.
158 | This can be changed by setting the `YouTubeDlVideoQuality` property on an instance of `MpvPlayer`.
159 | Note: this will take effect on the next call of `Load`.
160 |
161 | ## API
162 |
163 | This "API" is a wrapper around the mpv C API defined in [client.h](https://github.com/mpv-player/mpv/blob/master/libmpv/client.h) and is utilised by the player.
164 |
165 | ### Mpv
166 |
167 | Simplest way to create an instance of the mpv .NET API and load libmpv:
168 | ```csharp
169 | // Relative path to the DLL.
170 | var dllPath = @"lib\mpv-1.dll";
171 |
172 | using (var mpv = new Mpv(dllPath))
173 | {
174 | // code
175 | }
176 | ```
177 |
178 | This will provide a friendly interface to the mpv C API and start an event loop which will raise events accordingly.
179 |
180 | ### MpvFunctions
181 |
182 | If you are looking for a more direct approach to the C API you can create an instance of MpvFunctions which allows you to execute the loaded delegates directly:
183 | ```csharp
184 | var dllPath = @"lib\mpv-1.dll";
185 |
186 | using (var mpvFunctions = new MpvFunctions(dllPath))
187 | {
188 | // code
189 | }
190 | ```
191 |
192 | Be aware, this method does not raise events and an event loop would have to be manually implemented.
193 |
194 | ### MpvEventLoop
195 |
196 | If you are looking that start an event loop you will need to create an instance of MpvFunctions, create and initialise mpv, create an instance of MpvEventLoop and start it:
197 | ```csharp
198 | var dllPath = @"lib\mpv-1.dll";
199 |
200 | // Create an instance of MpvFunctions.
201 | var functions = new MpvFunctions(dllPath);
202 |
203 | // Create mpv
204 | var handle = functions.Create();
205 |
206 | // Initialise mpv
207 | functions.Initialise(handle);
208 |
209 | // Create an instance of MpvEventLoop, passing in a callback argument
210 | // which will be invoked when an event comes in.
211 | using (var eventLoop = new MpvEventLoop(callback, handle, functions))
212 | {
213 | // Start the event loop.
214 | eventLoop.Start();
215 | }
216 | ```
217 |
218 | The code above does not contain any error checking, most of the mpv functions return an MpvError which indicates whether an error has occured.
219 |
220 | ## Licensing
221 |
222 | The libmpv C API *specifically* is licensed under [ICS](https://choosealicense.com/licenses/isc/), this means that a wrapper such as this can be licensed under [MIT](https://choosealicense.com/licenses/mit/).
223 |
224 | The rest of libmpv is licensed under [GPLv2](https://choosealicense.com/licenses/gpl-2.0/) by default, which means that any work utilising this wrapper in conjunction with libmpv is subject to GPLv2, unless libmpv is compiled using [LGPL](https://choosealicense.com/licenses/lgpl-2.1/).
225 |
226 | In simple terms, once you use the "libmpv" files (DLL) you downloaded, your application must be licensed under GPLv2.
227 |
228 | See [here](https://github.com/mpv-player/mpv#license) for more information.
229 |
--------------------------------------------------------------------------------
/src/Mpv.NET.WPFExample/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/src/Mpv.NET.WPFExample/App.xaml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/Mpv.NET.WPFExample/App.xaml.cs:
--------------------------------------------------------------------------------
1 | using System.Windows;
2 |
3 | namespace Mpv.NET.WPFExample
4 | {
5 | public partial class App : Application
6 | {
7 | }
8 | }
--------------------------------------------------------------------------------
/src/Mpv.NET.WPFExample/MainWindow.xaml:
--------------------------------------------------------------------------------
1 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/Mpv.NET.WPFExample/MainWindow.xaml.cs:
--------------------------------------------------------------------------------
1 | using System.Windows;
2 | using Mpv.NET.Player;
3 |
4 | namespace Mpv.NET.WPFExample
5 | {
6 | public partial class MainWindow : Window
7 | {
8 | private MpvPlayer player;
9 |
10 | public MainWindow()
11 | {
12 | InitializeComponent();
13 |
14 | player = new MpvPlayer(PlayerHost.Handle)
15 | {
16 | Loop = true,
17 | Volume = 50
18 | };
19 | player.Load("http://techslides.com/demos/sample-videos/small.mp4");
20 | player.Resume();
21 | }
22 |
23 | private void WindowOnClosing(object sender, System.ComponentModel.CancelEventArgs e)
24 | {
25 | player.Dispose();
26 | }
27 | }
28 | }
--------------------------------------------------------------------------------
/src/Mpv.NET.WPFExample/Mpv.NET.WPFExample.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {8FE66FA3-7AC7-474E-B0F9-A052EACDD62C}
8 | WinExe
9 | Mpv.NET.WPFExample
10 | Mpv.NET.WPFExample
11 | v4.7
12 | 512
13 | {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
14 | 4
15 | true
16 |
17 |
18 | AnyCPU
19 | true
20 | full
21 | false
22 | bin\Debug\
23 | DEBUG;TRACE
24 | prompt
25 | 4
26 | true
27 |
28 |
29 | AnyCPU
30 | pdbonly
31 | true
32 | bin\Release\
33 | TRACE
34 | prompt
35 | 4
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 | 4.0
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 | MSBuild:Compile
60 | Designer
61 |
62 |
63 | MSBuild:Compile
64 | Designer
65 |
66 |
67 | App.xaml
68 | Code
69 |
70 |
71 | MainWindow.xaml
72 | Code
73 |
74 |
75 |
76 |
77 | Code
78 |
79 |
80 |
81 |
82 |
83 | Always
84 |
85 |
86 |
87 |
88 | {02d1e0d1-d9c6-4e2a-a598-7eca726b7166}
89 | Mpv.NET
90 |
91 |
92 |
93 |
94 | Always
95 |
96 |
97 | Always
98 |
99 |
100 |
101 |
--------------------------------------------------------------------------------
/src/Mpv.NET.WPFExample/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Resources;
3 | using System.Runtime.CompilerServices;
4 | using System.Runtime.InteropServices;
5 | using System.Windows;
6 |
7 | // General Information about an assembly is controlled through the following
8 | // set of attributes. Change these attribute values to modify the information
9 | // associated with an assembly.
10 | [assembly: AssemblyTitle("Mpv.NET.WPFExample")]
11 | [assembly: AssemblyDescription("")]
12 | [assembly: AssemblyConfiguration("")]
13 | [assembly: AssemblyCompany("")]
14 | [assembly: AssemblyProduct("Mpv.NET.WPFExample")]
15 | [assembly: AssemblyCopyright("Copyright © 2018")]
16 | [assembly: AssemblyTrademark("")]
17 | [assembly: AssemblyCulture("")]
18 |
19 | // Setting ComVisible to false makes the types in this assembly not visible
20 | // to COM components. If you need to access a type in this assembly from
21 | // COM, set the ComVisible attribute to true on that type.
22 | [assembly: ComVisible(false)]
23 |
24 | //In order to begin building localizable applications, set
25 | //CultureYouAreCodingWith in your .csproj file
26 | //inside a . For example, if you are using US english
27 | //in your source files, set the to en-US. Then uncomment
28 | //the NeutralResourceLanguage attribute below. Update the "en-US" in
29 | //the line below to match the UICulture setting in the project file.
30 |
31 | //[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)]
32 |
33 |
34 | [assembly: ThemeInfo(
35 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
36 | //(used if a resource is not found in the page,
37 | // or application resource dictionaries)
38 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
39 | //(used if a resource is not found in the page,
40 | // app, or any theme specific resource dictionaries)
41 | )]
42 |
43 |
44 | // Version information for an assembly consists of the following four values:
45 | //
46 | // Major Version
47 | // Minor Version
48 | // Build Number
49 | // Revision
50 | //
51 | // You can specify all the values or you can default the Build and Revision Numbers
52 | // by using the '*' as shown below:
53 | // [assembly: AssemblyVersion("1.0.*")]
54 | [assembly: AssemblyVersion("1.0.0.0")]
55 | [assembly: AssemblyFileVersion("1.0.0.0")]
56 |
--------------------------------------------------------------------------------
/src/Mpv.NET.WPFExample/lib/mpv-1.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hudec117/Mpv.NET-lib-/3a5181c847280f6a78cb51c77148cb1f7f3f33a1/src/Mpv.NET.WPFExample/lib/mpv-1.dll
--------------------------------------------------------------------------------
/src/Mpv.NET.WPFExample/lib/youtube-dl.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hudec117/Mpv.NET-lib-/3a5181c847280f6a78cb51c77148cb1f7f3f33a1/src/Mpv.NET.WPFExample/lib/youtube-dl.exe
--------------------------------------------------------------------------------
/src/Mpv.NET.WPFExample/lib/ytdl_hook.lua:
--------------------------------------------------------------------------------
1 | local utils = require 'mp.utils'
2 | local msg = require 'mp.msg'
3 | local options = require 'mp.options'
4 |
5 | local o = {
6 | exclude = "",
7 | try_ytdl_first = false,
8 | use_manifests = false
9 | }
10 | options.read_options(o)
11 |
12 | local ytdl = {
13 | path = "lib\\youtube-dl.exe",
14 | searched = false,
15 | blacklisted = {}
16 | }
17 |
18 | local chapter_list = {}
19 |
20 | function Set (t)
21 | local set = {}
22 | for _, v in pairs(t) do set[v] = true end
23 | return set
24 | end
25 |
26 | local safe_protos = Set {
27 | "http", "https", "ftp", "ftps",
28 | "rtmp", "rtmps", "rtmpe", "rtmpt", "rtmpts", "rtmpte",
29 | "data"
30 | }
31 |
32 | local function exec(args)
33 | local ret = utils.subprocess({args = args})
34 | return ret.status, ret.stdout, ret, ret.killed_by_us
35 | end
36 |
37 | -- return true if it was explicitly set on the command line
38 | local function option_was_set(name)
39 | return mp.get_property_bool("option-info/" ..name.. "/set-from-commandline",
40 | false)
41 | end
42 |
43 | -- return true if the option was set locally
44 | local function option_was_set_locally(name)
45 | return mp.get_property_bool("option-info/" ..name.. "/set-locally", false)
46 | end
47 |
48 | -- youtube-dl may set special http headers for some sites (user-agent, cookies)
49 | local function set_http_headers(http_headers)
50 | if not http_headers then
51 | return
52 | end
53 | local headers = {}
54 | local useragent = http_headers["User-Agent"]
55 | if useragent and not option_was_set("user-agent") then
56 | mp.set_property("file-local-options/user-agent", useragent)
57 | end
58 | local additional_fields = {"Cookie", "Referer", "X-Forwarded-For"}
59 | for idx, item in pairs(additional_fields) do
60 | local field_value = http_headers[item]
61 | if field_value then
62 | headers[#headers + 1] = item .. ": " .. field_value
63 | end
64 | end
65 | if #headers > 0 and not option_was_set("http-header-fields") then
66 | mp.set_property_native("file-local-options/http-header-fields", headers)
67 | end
68 | end
69 |
70 | local function append_libav_opt(props, name, value)
71 | if not props then
72 | props = {}
73 | end
74 |
75 | if name and value and not props[name] then
76 | props[name] = value
77 | end
78 |
79 | return props
80 | end
81 |
82 | local function edl_escape(url)
83 | return "%" .. string.len(url) .. "%" .. url
84 | end
85 |
86 | local function url_is_safe(url)
87 | local proto = type(url) == "string" and url:match("^(.+)://") or nil
88 | local safe = proto and safe_protos[proto]
89 | if not safe then
90 | msg.error(("Ignoring potentially unsafe url: '%s'"):format(url))
91 | end
92 | return safe
93 | end
94 |
95 | local function time_to_secs(time_string)
96 | local ret
97 |
98 | local a, b, c = time_string:match("(%d+):(%d%d?):(%d%d)")
99 | if a ~= nil then
100 | ret = (a*3600 + b*60 + c)
101 | else
102 | a, b = time_string:match("(%d%d?):(%d%d)")
103 | if a ~= nil then
104 | ret = (a*60 + b)
105 | end
106 | end
107 |
108 | return ret
109 | end
110 |
111 | local function extract_chapters(data, video_length)
112 | local ret = {}
113 |
114 | for line in data:gmatch("[^\r\n]+") do
115 | local time = time_to_secs(line)
116 | if time and (time < video_length) then
117 | table.insert(ret, {time = time, title = line})
118 | end
119 | end
120 | table.sort(ret, function(a, b) return a.time < b.time end)
121 | return ret
122 | end
123 |
124 | local function is_blacklisted(url)
125 | if o.exclude == "" then return false end
126 | if #ytdl.blacklisted == 0 then
127 | local joined = o.exclude
128 | while joined:match('%|?[^|]+') do
129 | local _, e, substring = joined:find('%|?([^|]+)')
130 | table.insert(ytdl.blacklisted, substring)
131 | joined = joined:sub(e+1)
132 | end
133 | end
134 | if #ytdl.blacklisted > 0 then
135 | url = url:match('https?://(.+)')
136 | for _, exclude in ipairs(ytdl.blacklisted) do
137 | if url:match(exclude) then
138 | msg.verbose('URL matches excluded substring. Skipping.')
139 | return true
140 | end
141 | end
142 | end
143 | return false
144 | end
145 |
146 | local function parse_yt_playlist(url, json)
147 | -- return 0-based index to use with --playlist-start
148 |
149 | if not json.extractor or json.extractor ~= "youtube:playlist" then
150 | return nil
151 | end
152 |
153 | local query = url:match("%?.+")
154 | if not query then return nil end
155 |
156 | local args = {}
157 | for arg, param in query:gmatch("(%a+)=([^&?]+)") do
158 | if arg and param then
159 | args[arg] = param
160 | end
161 | end
162 |
163 | local maybe_idx = tonumber(args["index"])
164 |
165 | -- if index matches v param it's probably the requested item
166 | if maybe_idx and #json.entries >= maybe_idx and
167 | json.entries[maybe_idx].id == args["v"] then
168 | msg.debug("index matches requested video")
169 | return maybe_idx - 1
170 | end
171 |
172 | -- if there's no index or it doesn't match, look for video
173 | for i = 1, #json.entries do
174 | if json.entries[i] == args["v"] then
175 | msg.debug("found requested video in index " .. (i - 1))
176 | return i - 1
177 | end
178 | end
179 |
180 | msg.debug("requested video not found in playlist")
181 | -- if item isn't on the playlist, give up
182 | return nil
183 | end
184 |
185 | local function make_absolute_url(base_url, url)
186 | if url:find("https?://") == 1 then return url end
187 |
188 | local proto, domain, rest =
189 | base_url:match("(https?://)([^/]+/)(.*)/?")
190 | local segs = {}
191 | rest:gsub("([^/]+)", function(c) table.insert(segs, c) end)
192 | url:gsub("([^/]+)", function(c) table.insert(segs, c) end)
193 | local resolved_url = {}
194 | for i, v in ipairs(segs) do
195 | if v == ".." then
196 | table.remove(resolved_url)
197 | elseif v ~= "." then
198 | table.insert(resolved_url, v)
199 | end
200 | end
201 | return proto .. domain ..
202 | table.concat(resolved_url, "/")
203 | end
204 |
205 | local function join_url(base_url, fragment)
206 | local res = ""
207 | if base_url and fragment.path then
208 | res = make_absolute_url(base_url, fragment.path)
209 | elseif fragment.url then
210 | res = fragment.url
211 | end
212 | return res
213 | end
214 |
215 | local function edl_track_joined(fragments, protocol, is_live, base)
216 | if not (type(fragments) == "table") or not fragments[1] then
217 | msg.debug("No fragments to join into EDL")
218 | return nil
219 | end
220 |
221 | local edl = "edl://"
222 | local offset = 1
223 | local parts = {}
224 |
225 | if (protocol == "http_dash_segments") and not is_live then
226 | msg.debug("Using dash")
227 | local args = ""
228 |
229 | -- assume MP4 DASH initialization segment
230 | if not fragments[1].duration then
231 | msg.debug("Using init segment")
232 | args = args .. ",init=" .. edl_escape(join_url(base, fragments[1]))
233 | offset = 2
234 | end
235 |
236 | table.insert(parts, "!mp4_dash" .. args)
237 |
238 | -- Check remaining fragments for duration;
239 | -- if not available in all, give up.
240 | for i = offset, #fragments do
241 | if not fragments[i].duration then
242 | msg.error("EDL doesn't support fragments" ..
243 | "without duration with MP4 DASH")
244 | return nil
245 | end
246 | end
247 | end
248 |
249 | for i = offset, #fragments do
250 | local fragment = fragments[i]
251 | if not url_is_safe(join_url(base, fragment)) then
252 | return nil
253 | end
254 | table.insert(parts, edl_escape(join_url(base, fragment)))
255 | if fragment.duration then
256 | parts[#parts] =
257 | parts[#parts] .. ",length="..fragment.duration
258 | end
259 | end
260 | return edl .. table.concat(parts, ";") .. ";"
261 | end
262 |
263 | local function has_native_dash_demuxer()
264 | local demuxers = mp.get_property_native("demuxer-lavf-list", {})
265 | for _, v in ipairs(demuxers) do
266 | if v == "dash" then
267 | return true
268 | end
269 | end
270 | return false
271 | end
272 |
273 | local function valid_manifest(json)
274 | local reqfmt = json["requested_formats"] and json["requested_formats"][1] or {}
275 | if not reqfmt["manifest_url"] and not json["manifest_url"] then
276 | return false
277 | end
278 | local proto = reqfmt["protocol"] or json["protocol"] or ""
279 | return (proto == "http_dash_segments" and has_native_dash_demuxer()) or
280 | proto:find("^m3u8")
281 | end
282 |
283 | local function add_single_video(json)
284 | local streamurl = ""
285 | local max_bitrate = 0
286 | local reqfmts = json["requested_formats"]
287 |
288 | -- prefer manifest_url if present
289 | if o.use_manifests and valid_manifest(json) then
290 | local mpd_url = reqfmts and reqfmts[1]["manifest_url"] or
291 | json["manifest_url"]
292 | if not mpd_url then
293 | msg.error("No manifest URL found in JSON data.")
294 | return
295 | elseif not url_is_safe(mpd_url) then
296 | return
297 | end
298 |
299 | streamurl = mpd_url
300 |
301 | if reqfmts then
302 | for _, track in pairs(reqfmts) do
303 | max_bitrate = track.tbr > max_bitrate and
304 | track.tbr or max_bitrate
305 | end
306 | elseif json.tbr then
307 | max_bitrate = json.tbr > max_bitrate and json.tbr or max_bitrate
308 | end
309 |
310 | -- DASH/split tracks
311 | elseif reqfmts then
312 | local streams = {}
313 |
314 | for _, track in pairs(reqfmts) do
315 | local edl_track = nil
316 | edl_track = edl_track_joined(track.fragments,
317 | track.protocol, json.is_live,
318 | track.fragment_base_url)
319 | if not edl_track and not url_is_safe(track.url) then
320 | return
321 | end
322 | if track.vcodec and track.vcodec ~= "none" then
323 | -- video track
324 | streams[#streams + 1] = edl_track or track.url
325 | elseif track.vcodec == "none" then
326 | -- audio track
327 | streams[#streams + 1] = edl_track or track.url
328 | end
329 | end
330 |
331 | if #streams > 1 then
332 | -- merge them via EDL
333 | for i = 1, #streams do
334 | streams[i] = "!no_clip;!no_chapters;" .. edl_escape(streams[i])
335 | end
336 | streamurl = "edl://" ..
337 | table.concat(streams, ";!new_stream;") .. ";"
338 | else
339 | streamurl = streams[1]
340 | end
341 |
342 | elseif not (json.url == nil) then
343 | local edl_track = nil
344 | edl_track = edl_track_joined(json.fragments, json.protocol,
345 | json.is_live, json.fragment_base_url)
346 |
347 | if not edl_track and not url_is_safe(json.url) then
348 | return
349 | end
350 | -- normal video or single track
351 | streamurl = edl_track or json.url
352 | set_http_headers(json.http_headers)
353 | else
354 | msg.error("No URL found in JSON data.")
355 | return
356 | end
357 |
358 | msg.debug("streamurl: " .. streamurl)
359 |
360 | mp.set_property("stream-open-filename", streamurl:gsub("^data:", "data://", 1))
361 |
362 | mp.set_property("file-local-options/force-media-title", json.title)
363 |
364 | -- set hls-bitrate for dash track selection
365 | if max_bitrate > 0 and
366 | not option_was_set("hls-bitrate") and
367 | not option_was_set_locally("hls-bitrate") then
368 | mp.set_property_native('file-local-options/hls-bitrate', max_bitrate*1000)
369 | end
370 |
371 | -- add subtitles
372 | if not (json.requested_subtitles == nil) then
373 | for lang, sub_info in pairs(json.requested_subtitles) do
374 | msg.verbose("adding subtitle ["..lang.."]")
375 |
376 | local sub = nil
377 |
378 | if not (sub_info.data == nil) then
379 | sub = "memory://"..sub_info.data
380 | elseif not (sub_info.url == nil) and
381 | url_is_safe(sub_info.url) then
382 | sub = sub_info.url
383 | end
384 |
385 | if not (sub == nil) then
386 | mp.commandv("sub-add", sub,
387 | "auto", sub_info.ext, lang)
388 | else
389 | msg.verbose("No subtitle data/url for ["..lang.."]")
390 | end
391 | end
392 | end
393 |
394 | -- add chapters
395 | if json.chapters then
396 | msg.debug("Adding pre-parsed chapters")
397 | for i = 1, #json.chapters do
398 | local chapter = json.chapters[i]
399 | local title = chapter.title or ""
400 | if title == "" then
401 | title = string.format('Chapter %02d', i)
402 | end
403 | table.insert(chapter_list, {time=chapter.start_time, title=title})
404 | end
405 | elseif not (json.description == nil) and not (json.duration == nil) then
406 | chapter_list = extract_chapters(json.description, json.duration)
407 | end
408 |
409 | -- set start time
410 | if not (json.start_time == nil) and
411 | not option_was_set("start") and
412 | not option_was_set_locally("start") then
413 | msg.debug("Setting start to: " .. json.start_time .. " secs")
414 | mp.set_property("file-local-options/start", json.start_time)
415 | end
416 |
417 | -- set aspect ratio for anamorphic video
418 | if not (json.stretched_ratio == nil) and
419 | not option_was_set("video-aspect") then
420 | mp.set_property('file-local-options/video-aspect', json.stretched_ratio)
421 | end
422 |
423 | local stream_opts = mp.get_property_native("file-local-options/stream-lavf-o", {})
424 |
425 | -- for rtmp
426 | if (json.protocol == "rtmp") then
427 | stream_opts = append_libav_opt(stream_opts,
428 | "rtmp_tcurl", streamurl)
429 | stream_opts = append_libav_opt(stream_opts,
430 | "rtmp_pageurl", json.page_url)
431 | stream_opts = append_libav_opt(stream_opts,
432 | "rtmp_playpath", json.play_path)
433 | stream_opts = append_libav_opt(stream_opts,
434 | "rtmp_swfverify", json.player_url)
435 | stream_opts = append_libav_opt(stream_opts,
436 | "rtmp_swfurl", json.player_url)
437 | stream_opts = append_libav_opt(stream_opts,
438 | "rtmp_app", json.app)
439 | end
440 |
441 | if json.proxy and json.proxy ~= "" then
442 | stream_opts = append_libav_opt(stream_opts,
443 | "http_proxy", json.proxy)
444 | end
445 |
446 | mp.set_property_native("file-local-options/stream-lavf-o", stream_opts)
447 | end
448 |
449 | function run_ytdl_hook(url)
450 | local start_time = os.clock()
451 |
452 | -- check for youtube-dl in mpv's config dir
453 | if not (ytdl.searched) then
454 | local exesuf = (package.config:sub(1,1) == '\\') and '.exe' or ''
455 | local ytdl_mcd = mp.find_config_file("youtube-dl" .. exesuf)
456 | if not (ytdl_mcd == nil) then
457 | msg.verbose("found youtube-dl at: " .. ytdl_mcd)
458 | ytdl.path = ytdl_mcd
459 | end
460 | ytdl.searched = true
461 | end
462 |
463 | -- strip ytdl://
464 | if (url:find("ytdl://") == 1) then
465 | url = url:sub(8)
466 | end
467 |
468 | local format = mp.get_property("options/ytdl-format")
469 | local raw_options = mp.get_property_native("options/ytdl-raw-options")
470 | local allsubs = true
471 | local proxy = nil
472 | local use_playlist = false
473 |
474 | local command = {
475 | ytdl.path, "--no-warnings", "-J", "--flat-playlist",
476 | "--sub-format", "ass/srt/best"
477 | }
478 |
479 | -- Checks if video option is "no", change format accordingly,
480 | -- but only if user didn't explicitly set one
481 | if (mp.get_property("options/vid") == "no")
482 | and not option_was_set("ytdl-format") then
483 |
484 | format = "bestaudio/best"
485 | msg.verbose("Video disabled. Only using audio")
486 | end
487 |
488 | if (format == "") then
489 | format = "bestvideo+bestaudio/best"
490 | end
491 | table.insert(command, "--format")
492 | table.insert(command, format)
493 |
494 | for param, arg in pairs(raw_options) do
495 | table.insert(command, "--" .. param)
496 | if (arg ~= "") then
497 | table.insert(command, arg)
498 | end
499 | if (param == "sub-lang") and (arg ~= "") then
500 | allsubs = false
501 | elseif (param == "proxy") and (arg ~= "") then
502 | proxy = arg
503 | elseif (param == "yes-playlist") then
504 | use_playlist = true
505 | end
506 | end
507 |
508 | if (allsubs == true) then
509 | table.insert(command, "--all-subs")
510 | end
511 | if not use_playlist then
512 | table.insert(command, "--no-playlist")
513 | end
514 | table.insert(command, "--")
515 | table.insert(command, url)
516 | msg.debug("Running: " .. table.concat(command,' '))
517 | local es, json, result, aborted = exec(command)
518 |
519 | if aborted then
520 | return
521 | end
522 |
523 | if (es < 0) or (json == nil) or (json == "") then
524 | local err = "youtube-dl failed: "
525 | if result.error and result.error == "init" then
526 | err = err .. "not found or not enough permissions"
527 | elseif not result.killed_by_us then
528 | err = err .. "unexpected error ocurred"
529 | else
530 | err = string.format("%s returned '%d'", err, es)
531 | end
532 | msg.error(err)
533 | return
534 | end
535 |
536 | local json, err = utils.parse_json(json)
537 |
538 | if (json == nil) then
539 | msg.error("failed to parse JSON data: " .. err)
540 | return
541 | end
542 |
543 | msg.verbose("youtube-dl succeeded!")
544 | msg.debug('ytdl parsing took '..os.clock()-start_time..' seconds')
545 |
546 | json["proxy"] = json["proxy"] or proxy
547 |
548 | -- what did we get?
549 | if json["direct"] then
550 | -- direct URL, nothing to do
551 | msg.verbose("Got direct URL")
552 | return
553 | elseif (json["_type"] == "playlist")
554 | or (json["_type"] == "multi_video") then
555 | -- a playlist
556 |
557 | if (#json.entries == 0) then
558 | msg.warn("Got empty playlist, nothing to play.")
559 | return
560 | end
561 |
562 | local self_redirecting_url =
563 | json.entries[1]["_type"] ~= "url_transparent" and
564 | json.entries[1]["webpage_url"] and
565 | json.entries[1]["webpage_url"] == json["webpage_url"]
566 |
567 |
568 | -- some funky guessing to detect multi-arc videos
569 | if self_redirecting_url and #json.entries > 1
570 | and json.entries[1].protocol == "m3u8_native"
571 | and json.entries[1].url then
572 | msg.verbose("multi-arc video detected, building EDL")
573 |
574 | local playlist = edl_track_joined(json.entries)
575 |
576 | msg.debug("EDL: " .. playlist)
577 |
578 | if not playlist then
579 | return
580 | end
581 |
582 | -- can't change the http headers for each entry, so use the 1st
583 | set_http_headers(json.entries[1].http_headers)
584 |
585 | mp.set_property("stream-open-filename", playlist)
586 | if not (json.title == nil) then
587 | mp.set_property("file-local-options/force-media-title",
588 | json.title)
589 | end
590 |
591 | -- there might not be subs for the first segment
592 | local entry_wsubs = nil
593 | for i, entry in pairs(json.entries) do
594 | if not (entry.requested_subtitles == nil) then
595 | entry_wsubs = i
596 | break
597 | end
598 | end
599 |
600 | if not (entry_wsubs == nil) and
601 | not (json.entries[entry_wsubs].duration == nil) then
602 | for j, req in pairs(json.entries[entry_wsubs].requested_subtitles) do
603 | local subfile = "edl://"
604 | for i, entry in pairs(json.entries) do
605 | if not (entry.requested_subtitles == nil) and
606 | not (entry.requested_subtitles[j] == nil) and
607 | url_is_safe(entry.requested_subtitles[j].url) then
608 | subfile = subfile..edl_escape(entry.requested_subtitles[j].url)
609 | else
610 | subfile = subfile..edl_escape("memory://WEBVTT")
611 | end
612 | subfile = subfile..",length="..entry.duration..";"
613 | end
614 | msg.debug(j.." sub EDL: "..subfile)
615 | mp.commandv("sub-add", subfile, "auto", req.ext, j)
616 | end
617 | end
618 |
619 | elseif self_redirecting_url and #json.entries == 1 then
620 | msg.verbose("Playlist with single entry detected.")
621 | add_single_video(json.entries[1])
622 | else
623 | local playlist_index = parse_yt_playlist(url, json)
624 | local playlist = {"#EXTM3U"}
625 | for i, entry in pairs(json.entries) do
626 | local site = entry.url
627 | local title = entry.title
628 |
629 | if not (title == nil) then
630 | title = string.gsub(title, '%s+', ' ')
631 | table.insert(playlist, "#EXTINF:0," .. title)
632 | end
633 |
634 | --[[ some extractors will still return the full info for
635 | all clips in the playlist and the URL will point
636 | directly to the file in that case, which we don't
637 | want so get the webpage URL instead, which is what
638 | we want, but only if we aren't going to trigger an
639 | infinite loop
640 | --]]
641 | if entry["webpage_url"] and not self_redirecting_url then
642 | site = entry["webpage_url"]
643 | end
644 |
645 | -- links without protocol as returned by --flat-playlist
646 | if not site:find("://") then
647 | -- youtube extractor provides only IDs,
648 | -- others come prefixed with the extractor name and ":"
649 | local prefix = site:find(":") and "ytdl://" or
650 | "https://youtu.be/"
651 | table.insert(playlist, prefix .. site)
652 | elseif url_is_safe(site) then
653 | table.insert(playlist, site)
654 | end
655 |
656 | end
657 |
658 | if use_playlist and
659 | not option_was_set("playlist-start") and playlist_index then
660 | mp.set_property_number("playlist-start", playlist_index)
661 | end
662 |
663 | mp.set_property("stream-open-filename", "memory://" .. table.concat(playlist, "\n"))
664 |
665 | -- This disables mpv's mushy playlist security code, which will
666 | -- break links that will be resolved to EDL later (because EDL is
667 | -- not considered "safe", and the playlist entries got tagged as
668 | -- network originating due to the playlist redirection).
669 | mp.set_property_native("file-local-options/load-unsafe-playlists", true)
670 | end
671 |
672 | else -- probably a video
673 | add_single_video(json)
674 | end
675 | msg.debug('script running time: '..os.clock()-start_time..' seconds')
676 | end
677 |
678 | if (not o.try_ytdl_first) then
679 | mp.add_hook("on_load", 10, function ()
680 | msg.verbose('ytdl:// hook')
681 | local url = mp.get_property("stream-open-filename", "")
682 | if not (url:find("ytdl://") == 1) then
683 | msg.verbose('not a ytdl:// url')
684 | return
685 | end
686 | run_ytdl_hook(url)
687 | end)
688 | end
689 |
690 | mp.add_hook(o.try_ytdl_first and "on_load" or "on_load_fail", 10, function()
691 | msg.verbose('full hook')
692 | local url = mp.get_property("stream-open-filename", "")
693 | if not (url:find("ytdl://") == 1) and
694 | not ((url:find("https?://") == 1) and not is_blacklisted(url)) then
695 | return
696 | end
697 | run_ytdl_hook(url)
698 | end)
699 |
700 | mp.add_hook("on_preloaded", 10, function ()
701 | if next(chapter_list) ~= nil then
702 | msg.verbose("Setting chapters")
703 |
704 | mp.set_property_native("chapter-list", chapter_list)
705 | chapter_list = {}
706 | end
707 | end)
708 |
--------------------------------------------------------------------------------
/src/Mpv.NET.WinFormsExample/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/src/Mpv.NET.WinFormsExample/MainForm.Designer.cs:
--------------------------------------------------------------------------------
1 | namespace Mpv.NET.WinFormsExample
2 | {
3 | partial class MainForm
4 | {
5 | ///
6 | /// Required designer variable.
7 | ///
8 | private System.ComponentModel.IContainer components = null;
9 |
10 | ///
11 | /// Clean up any resources being used.
12 | ///
13 | /// true if managed resources should be disposed; otherwise, false.
14 | protected override void Dispose(bool disposing)
15 | {
16 | if (disposing && (components != null))
17 | {
18 | components.Dispose();
19 | }
20 | base.Dispose(disposing);
21 | }
22 |
23 | #region Windows Form Designer generated code
24 |
25 | ///
26 | /// Required method for Designer support - do not modify
27 | /// the contents of this method with the code editor.
28 | ///
29 | private void InitializeComponent()
30 | {
31 | this.SuspendLayout();
32 | //
33 | // MainForm
34 | //
35 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
36 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
37 | this.ClientSize = new System.Drawing.Size(784, 421);
38 | this.Name = "MainForm";
39 | this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.MainFormOnFormClosing);
40 | this.ResumeLayout(false);
41 |
42 | }
43 |
44 | #endregion
45 | }
46 | }
47 |
48 |
--------------------------------------------------------------------------------
/src/Mpv.NET.WinFormsExample/MainForm.cs:
--------------------------------------------------------------------------------
1 | using Mpv.NET.Player;
2 | using System.Windows.Forms;
3 |
4 | namespace Mpv.NET.WinFormsExample
5 | {
6 | public partial class MainForm : Form
7 | {
8 | private MpvPlayer player;
9 |
10 | public MainForm()
11 | {
12 | InitializeComponent();
13 |
14 | player = new MpvPlayer(this.Handle)
15 | {
16 | Loop = true,
17 | Volume = 50
18 | };
19 | player.Load(@"http://techslides.com/demos/sample-videos/small.mp4");
20 | player.Resume();
21 | }
22 |
23 | private void MainFormOnFormClosing(object sender, FormClosingEventArgs e)
24 | {
25 | player.Dispose();
26 | }
27 | }
28 | }
--------------------------------------------------------------------------------
/src/Mpv.NET.WinFormsExample/MainForm.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | text/microsoft-resx
110 |
111 |
112 | 2.0
113 |
114 |
115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
119 |
120 |
--------------------------------------------------------------------------------
/src/Mpv.NET.WinFormsExample/Mpv.NET.WinFormsExample.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {359B1FAB-E90E-40E3-A285-BCE50F671E14}
8 | WinExe
9 | Mpv.NET.WinFormsExample
10 | Mpv.NET.WinFormsExample
11 | v4.7
12 | 512
13 | true
14 |
15 |
16 | AnyCPU
17 | true
18 | full
19 | false
20 | bin\Debug\
21 | DEBUG;TRACE
22 | prompt
23 | 4
24 |
25 |
26 | AnyCPU
27 | pdbonly
28 | true
29 | bin\Release\
30 | TRACE
31 | prompt
32 | 4
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 | Form
50 |
51 |
52 | MainForm.cs
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 | Always
61 |
62 |
63 |
64 |
65 | MainForm.cs
66 |
67 |
68 |
69 |
70 | {02d1e0d1-d9c6-4e2a-a598-7eca726b7166}
71 | Mpv.NET
72 |
73 |
74 |
75 |
76 | Always
77 |
78 |
79 | Always
80 |
81 |
82 |
83 |
--------------------------------------------------------------------------------
/src/Mpv.NET.WinFormsExample/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Windows.Forms;
3 |
4 | namespace Mpv.NET.WinFormsExample
5 | {
6 | public static class Program
7 | {
8 | [STAThread]
9 | private static void Main()
10 | {
11 | Application.EnableVisualStyles();
12 | Application.SetCompatibleTextRenderingDefault(false);
13 | Application.Run(new MainForm());
14 | }
15 | }
16 | }
--------------------------------------------------------------------------------
/src/Mpv.NET.WinFormsExample/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // General Information about an assembly is controlled through the following
6 | // set of attributes. Change these attribute values to modify the information
7 | // associated with an assembly.
8 | [assembly: AssemblyTitle("Mpv.NET.WinFormsExample")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("Mpv.NET.WinFormsExample")]
13 | [assembly: AssemblyCopyright("Copyright © 2018")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // Setting ComVisible to false makes the types in this assembly not visible
18 | // to COM components. If you need to access a type in this assembly from
19 | // COM, set the ComVisible attribute to true on that type.
20 | [assembly: ComVisible(false)]
21 |
22 | // The following GUID is for the ID of the typelib if this project is exposed to COM
23 | [assembly: Guid("359b1fab-e90e-40e3-a285-bce50f671e14")]
24 |
25 | // Version information for an assembly consists of the following four values:
26 | //
27 | // Major Version
28 | // Minor Version
29 | // Build Number
30 | // Revision
31 | //
32 | // You can specify all the values or you can default the Build and Revision Numbers
33 | // by using the '*' as shown below:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.0.0.0")]
36 | [assembly: AssemblyFileVersion("1.0.0.0")]
37 |
--------------------------------------------------------------------------------
/src/Mpv.NET.WinFormsExample/lib/mpv-1.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hudec117/Mpv.NET-lib-/3a5181c847280f6a78cb51c77148cb1f7f3f33a1/src/Mpv.NET.WinFormsExample/lib/mpv-1.dll
--------------------------------------------------------------------------------
/src/Mpv.NET.WinFormsExample/lib/youtube-dl.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hudec117/Mpv.NET-lib-/3a5181c847280f6a78cb51c77148cb1f7f3f33a1/src/Mpv.NET.WinFormsExample/lib/youtube-dl.exe
--------------------------------------------------------------------------------
/src/Mpv.NET.WinFormsExample/lib/ytdl_hook.lua:
--------------------------------------------------------------------------------
1 | local utils = require 'mp.utils'
2 | local msg = require 'mp.msg'
3 | local options = require 'mp.options'
4 |
5 | local o = {
6 | exclude = "",
7 | try_ytdl_first = false,
8 | use_manifests = false
9 | }
10 | options.read_options(o)
11 |
12 | local ytdl = {
13 | path = "lib\\youtube-dl.exe",
14 | searched = false,
15 | blacklisted = {}
16 | }
17 |
18 | local chapter_list = {}
19 |
20 | function Set (t)
21 | local set = {}
22 | for _, v in pairs(t) do set[v] = true end
23 | return set
24 | end
25 |
26 | local safe_protos = Set {
27 | "http", "https", "ftp", "ftps",
28 | "rtmp", "rtmps", "rtmpe", "rtmpt", "rtmpts", "rtmpte",
29 | "data"
30 | }
31 |
32 | local function exec(args)
33 | local ret = utils.subprocess({args = args})
34 | return ret.status, ret.stdout, ret, ret.killed_by_us
35 | end
36 |
37 | -- return true if it was explicitly set on the command line
38 | local function option_was_set(name)
39 | return mp.get_property_bool("option-info/" ..name.. "/set-from-commandline",
40 | false)
41 | end
42 |
43 | -- return true if the option was set locally
44 | local function option_was_set_locally(name)
45 | return mp.get_property_bool("option-info/" ..name.. "/set-locally", false)
46 | end
47 |
48 | -- youtube-dl may set special http headers for some sites (user-agent, cookies)
49 | local function set_http_headers(http_headers)
50 | if not http_headers then
51 | return
52 | end
53 | local headers = {}
54 | local useragent = http_headers["User-Agent"]
55 | if useragent and not option_was_set("user-agent") then
56 | mp.set_property("file-local-options/user-agent", useragent)
57 | end
58 | local additional_fields = {"Cookie", "Referer", "X-Forwarded-For"}
59 | for idx, item in pairs(additional_fields) do
60 | local field_value = http_headers[item]
61 | if field_value then
62 | headers[#headers + 1] = item .. ": " .. field_value
63 | end
64 | end
65 | if #headers > 0 and not option_was_set("http-header-fields") then
66 | mp.set_property_native("file-local-options/http-header-fields", headers)
67 | end
68 | end
69 |
70 | local function append_libav_opt(props, name, value)
71 | if not props then
72 | props = {}
73 | end
74 |
75 | if name and value and not props[name] then
76 | props[name] = value
77 | end
78 |
79 | return props
80 | end
81 |
82 | local function edl_escape(url)
83 | return "%" .. string.len(url) .. "%" .. url
84 | end
85 |
86 | local function url_is_safe(url)
87 | local proto = type(url) == "string" and url:match("^(.+)://") or nil
88 | local safe = proto and safe_protos[proto]
89 | if not safe then
90 | msg.error(("Ignoring potentially unsafe url: '%s'"):format(url))
91 | end
92 | return safe
93 | end
94 |
95 | local function time_to_secs(time_string)
96 | local ret
97 |
98 | local a, b, c = time_string:match("(%d+):(%d%d?):(%d%d)")
99 | if a ~= nil then
100 | ret = (a*3600 + b*60 + c)
101 | else
102 | a, b = time_string:match("(%d%d?):(%d%d)")
103 | if a ~= nil then
104 | ret = (a*60 + b)
105 | end
106 | end
107 |
108 | return ret
109 | end
110 |
111 | local function extract_chapters(data, video_length)
112 | local ret = {}
113 |
114 | for line in data:gmatch("[^\r\n]+") do
115 | local time = time_to_secs(line)
116 | if time and (time < video_length) then
117 | table.insert(ret, {time = time, title = line})
118 | end
119 | end
120 | table.sort(ret, function(a, b) return a.time < b.time end)
121 | return ret
122 | end
123 |
124 | local function is_blacklisted(url)
125 | if o.exclude == "" then return false end
126 | if #ytdl.blacklisted == 0 then
127 | local joined = o.exclude
128 | while joined:match('%|?[^|]+') do
129 | local _, e, substring = joined:find('%|?([^|]+)')
130 | table.insert(ytdl.blacklisted, substring)
131 | joined = joined:sub(e+1)
132 | end
133 | end
134 | if #ytdl.blacklisted > 0 then
135 | url = url:match('https?://(.+)')
136 | for _, exclude in ipairs(ytdl.blacklisted) do
137 | if url:match(exclude) then
138 | msg.verbose('URL matches excluded substring. Skipping.')
139 | return true
140 | end
141 | end
142 | end
143 | return false
144 | end
145 |
146 | local function parse_yt_playlist(url, json)
147 | -- return 0-based index to use with --playlist-start
148 |
149 | if not json.extractor or json.extractor ~= "youtube:playlist" then
150 | return nil
151 | end
152 |
153 | local query = url:match("%?.+")
154 | if not query then return nil end
155 |
156 | local args = {}
157 | for arg, param in query:gmatch("(%a+)=([^&?]+)") do
158 | if arg and param then
159 | args[arg] = param
160 | end
161 | end
162 |
163 | local maybe_idx = tonumber(args["index"])
164 |
165 | -- if index matches v param it's probably the requested item
166 | if maybe_idx and #json.entries >= maybe_idx and
167 | json.entries[maybe_idx].id == args["v"] then
168 | msg.debug("index matches requested video")
169 | return maybe_idx - 1
170 | end
171 |
172 | -- if there's no index or it doesn't match, look for video
173 | for i = 1, #json.entries do
174 | if json.entries[i] == args["v"] then
175 | msg.debug("found requested video in index " .. (i - 1))
176 | return i - 1
177 | end
178 | end
179 |
180 | msg.debug("requested video not found in playlist")
181 | -- if item isn't on the playlist, give up
182 | return nil
183 | end
184 |
185 | local function make_absolute_url(base_url, url)
186 | if url:find("https?://") == 1 then return url end
187 |
188 | local proto, domain, rest =
189 | base_url:match("(https?://)([^/]+/)(.*)/?")
190 | local segs = {}
191 | rest:gsub("([^/]+)", function(c) table.insert(segs, c) end)
192 | url:gsub("([^/]+)", function(c) table.insert(segs, c) end)
193 | local resolved_url = {}
194 | for i, v in ipairs(segs) do
195 | if v == ".." then
196 | table.remove(resolved_url)
197 | elseif v ~= "." then
198 | table.insert(resolved_url, v)
199 | end
200 | end
201 | return proto .. domain ..
202 | table.concat(resolved_url, "/")
203 | end
204 |
205 | local function join_url(base_url, fragment)
206 | local res = ""
207 | if base_url and fragment.path then
208 | res = make_absolute_url(base_url, fragment.path)
209 | elseif fragment.url then
210 | res = fragment.url
211 | end
212 | return res
213 | end
214 |
215 | local function edl_track_joined(fragments, protocol, is_live, base)
216 | if not (type(fragments) == "table") or not fragments[1] then
217 | msg.debug("No fragments to join into EDL")
218 | return nil
219 | end
220 |
221 | local edl = "edl://"
222 | local offset = 1
223 | local parts = {}
224 |
225 | if (protocol == "http_dash_segments") and not is_live then
226 | msg.debug("Using dash")
227 | local args = ""
228 |
229 | -- assume MP4 DASH initialization segment
230 | if not fragments[1].duration then
231 | msg.debug("Using init segment")
232 | args = args .. ",init=" .. edl_escape(join_url(base, fragments[1]))
233 | offset = 2
234 | end
235 |
236 | table.insert(parts, "!mp4_dash" .. args)
237 |
238 | -- Check remaining fragments for duration;
239 | -- if not available in all, give up.
240 | for i = offset, #fragments do
241 | if not fragments[i].duration then
242 | msg.error("EDL doesn't support fragments" ..
243 | "without duration with MP4 DASH")
244 | return nil
245 | end
246 | end
247 | end
248 |
249 | for i = offset, #fragments do
250 | local fragment = fragments[i]
251 | if not url_is_safe(join_url(base, fragment)) then
252 | return nil
253 | end
254 | table.insert(parts, edl_escape(join_url(base, fragment)))
255 | if fragment.duration then
256 | parts[#parts] =
257 | parts[#parts] .. ",length="..fragment.duration
258 | end
259 | end
260 | return edl .. table.concat(parts, ";") .. ";"
261 | end
262 |
263 | local function has_native_dash_demuxer()
264 | local demuxers = mp.get_property_native("demuxer-lavf-list", {})
265 | for _, v in ipairs(demuxers) do
266 | if v == "dash" then
267 | return true
268 | end
269 | end
270 | return false
271 | end
272 |
273 | local function valid_manifest(json)
274 | local reqfmt = json["requested_formats"] and json["requested_formats"][1] or {}
275 | if not reqfmt["manifest_url"] and not json["manifest_url"] then
276 | return false
277 | end
278 | local proto = reqfmt["protocol"] or json["protocol"] or ""
279 | return (proto == "http_dash_segments" and has_native_dash_demuxer()) or
280 | proto:find("^m3u8")
281 | end
282 |
283 | local function add_single_video(json)
284 | local streamurl = ""
285 | local max_bitrate = 0
286 | local reqfmts = json["requested_formats"]
287 |
288 | -- prefer manifest_url if present
289 | if o.use_manifests and valid_manifest(json) then
290 | local mpd_url = reqfmts and reqfmts[1]["manifest_url"] or
291 | json["manifest_url"]
292 | if not mpd_url then
293 | msg.error("No manifest URL found in JSON data.")
294 | return
295 | elseif not url_is_safe(mpd_url) then
296 | return
297 | end
298 |
299 | streamurl = mpd_url
300 |
301 | if reqfmts then
302 | for _, track in pairs(reqfmts) do
303 | max_bitrate = track.tbr > max_bitrate and
304 | track.tbr or max_bitrate
305 | end
306 | elseif json.tbr then
307 | max_bitrate = json.tbr > max_bitrate and json.tbr or max_bitrate
308 | end
309 |
310 | -- DASH/split tracks
311 | elseif reqfmts then
312 | local streams = {}
313 |
314 | for _, track in pairs(reqfmts) do
315 | local edl_track = nil
316 | edl_track = edl_track_joined(track.fragments,
317 | track.protocol, json.is_live,
318 | track.fragment_base_url)
319 | if not edl_track and not url_is_safe(track.url) then
320 | return
321 | end
322 | if track.vcodec and track.vcodec ~= "none" then
323 | -- video track
324 | streams[#streams + 1] = edl_track or track.url
325 | elseif track.vcodec == "none" then
326 | -- audio track
327 | streams[#streams + 1] = edl_track or track.url
328 | end
329 | end
330 |
331 | if #streams > 1 then
332 | -- merge them via EDL
333 | for i = 1, #streams do
334 | streams[i] = "!no_clip;!no_chapters;" .. edl_escape(streams[i])
335 | end
336 | streamurl = "edl://" ..
337 | table.concat(streams, ";!new_stream;") .. ";"
338 | else
339 | streamurl = streams[1]
340 | end
341 |
342 | elseif not (json.url == nil) then
343 | local edl_track = nil
344 | edl_track = edl_track_joined(json.fragments, json.protocol,
345 | json.is_live, json.fragment_base_url)
346 |
347 | if not edl_track and not url_is_safe(json.url) then
348 | return
349 | end
350 | -- normal video or single track
351 | streamurl = edl_track or json.url
352 | set_http_headers(json.http_headers)
353 | else
354 | msg.error("No URL found in JSON data.")
355 | return
356 | end
357 |
358 | msg.debug("streamurl: " .. streamurl)
359 |
360 | mp.set_property("stream-open-filename", streamurl:gsub("^data:", "data://", 1))
361 |
362 | mp.set_property("file-local-options/force-media-title", json.title)
363 |
364 | -- set hls-bitrate for dash track selection
365 | if max_bitrate > 0 and
366 | not option_was_set("hls-bitrate") and
367 | not option_was_set_locally("hls-bitrate") then
368 | mp.set_property_native('file-local-options/hls-bitrate', max_bitrate*1000)
369 | end
370 |
371 | -- add subtitles
372 | if not (json.requested_subtitles == nil) then
373 | for lang, sub_info in pairs(json.requested_subtitles) do
374 | msg.verbose("adding subtitle ["..lang.."]")
375 |
376 | local sub = nil
377 |
378 | if not (sub_info.data == nil) then
379 | sub = "memory://"..sub_info.data
380 | elseif not (sub_info.url == nil) and
381 | url_is_safe(sub_info.url) then
382 | sub = sub_info.url
383 | end
384 |
385 | if not (sub == nil) then
386 | mp.commandv("sub-add", sub,
387 | "auto", sub_info.ext, lang)
388 | else
389 | msg.verbose("No subtitle data/url for ["..lang.."]")
390 | end
391 | end
392 | end
393 |
394 | -- add chapters
395 | if json.chapters then
396 | msg.debug("Adding pre-parsed chapters")
397 | for i = 1, #json.chapters do
398 | local chapter = json.chapters[i]
399 | local title = chapter.title or ""
400 | if title == "" then
401 | title = string.format('Chapter %02d', i)
402 | end
403 | table.insert(chapter_list, {time=chapter.start_time, title=title})
404 | end
405 | elseif not (json.description == nil) and not (json.duration == nil) then
406 | chapter_list = extract_chapters(json.description, json.duration)
407 | end
408 |
409 | -- set start time
410 | if not (json.start_time == nil) and
411 | not option_was_set("start") and
412 | not option_was_set_locally("start") then
413 | msg.debug("Setting start to: " .. json.start_time .. " secs")
414 | mp.set_property("file-local-options/start", json.start_time)
415 | end
416 |
417 | -- set aspect ratio for anamorphic video
418 | if not (json.stretched_ratio == nil) and
419 | not option_was_set("video-aspect") then
420 | mp.set_property('file-local-options/video-aspect', json.stretched_ratio)
421 | end
422 |
423 | local stream_opts = mp.get_property_native("file-local-options/stream-lavf-o", {})
424 |
425 | -- for rtmp
426 | if (json.protocol == "rtmp") then
427 | stream_opts = append_libav_opt(stream_opts,
428 | "rtmp_tcurl", streamurl)
429 | stream_opts = append_libav_opt(stream_opts,
430 | "rtmp_pageurl", json.page_url)
431 | stream_opts = append_libav_opt(stream_opts,
432 | "rtmp_playpath", json.play_path)
433 | stream_opts = append_libav_opt(stream_opts,
434 | "rtmp_swfverify", json.player_url)
435 | stream_opts = append_libav_opt(stream_opts,
436 | "rtmp_swfurl", json.player_url)
437 | stream_opts = append_libav_opt(stream_opts,
438 | "rtmp_app", json.app)
439 | end
440 |
441 | if json.proxy and json.proxy ~= "" then
442 | stream_opts = append_libav_opt(stream_opts,
443 | "http_proxy", json.proxy)
444 | end
445 |
446 | mp.set_property_native("file-local-options/stream-lavf-o", stream_opts)
447 | end
448 |
449 | function run_ytdl_hook(url)
450 | local start_time = os.clock()
451 |
452 | -- check for youtube-dl in mpv's config dir
453 | if not (ytdl.searched) then
454 | local exesuf = (package.config:sub(1,1) == '\\') and '.exe' or ''
455 | local ytdl_mcd = mp.find_config_file("youtube-dl" .. exesuf)
456 | if not (ytdl_mcd == nil) then
457 | msg.verbose("found youtube-dl at: " .. ytdl_mcd)
458 | ytdl.path = ytdl_mcd
459 | end
460 | ytdl.searched = true
461 | end
462 |
463 | -- strip ytdl://
464 | if (url:find("ytdl://") == 1) then
465 | url = url:sub(8)
466 | end
467 |
468 | local format = mp.get_property("options/ytdl-format")
469 | local raw_options = mp.get_property_native("options/ytdl-raw-options")
470 | local allsubs = true
471 | local proxy = nil
472 | local use_playlist = false
473 |
474 | local command = {
475 | ytdl.path, "--no-warnings", "-J", "--flat-playlist",
476 | "--sub-format", "ass/srt/best"
477 | }
478 |
479 | -- Checks if video option is "no", change format accordingly,
480 | -- but only if user didn't explicitly set one
481 | if (mp.get_property("options/vid") == "no")
482 | and not option_was_set("ytdl-format") then
483 |
484 | format = "bestaudio/best"
485 | msg.verbose("Video disabled. Only using audio")
486 | end
487 |
488 | if (format == "") then
489 | format = "bestvideo+bestaudio/best"
490 | end
491 | table.insert(command, "--format")
492 | table.insert(command, format)
493 |
494 | for param, arg in pairs(raw_options) do
495 | table.insert(command, "--" .. param)
496 | if (arg ~= "") then
497 | table.insert(command, arg)
498 | end
499 | if (param == "sub-lang") and (arg ~= "") then
500 | allsubs = false
501 | elseif (param == "proxy") and (arg ~= "") then
502 | proxy = arg
503 | elseif (param == "yes-playlist") then
504 | use_playlist = true
505 | end
506 | end
507 |
508 | if (allsubs == true) then
509 | table.insert(command, "--all-subs")
510 | end
511 | if not use_playlist then
512 | table.insert(command, "--no-playlist")
513 | end
514 | table.insert(command, "--")
515 | table.insert(command, url)
516 | msg.debug("Running: " .. table.concat(command,' '))
517 | local es, json, result, aborted = exec(command)
518 |
519 | if aborted then
520 | return
521 | end
522 |
523 | if (es < 0) or (json == nil) or (json == "") then
524 | local err = "youtube-dl failed: "
525 | if result.error and result.error == "init" then
526 | err = err .. "not found or not enough permissions"
527 | elseif not result.killed_by_us then
528 | err = err .. "unexpected error ocurred"
529 | else
530 | err = string.format("%s returned '%d'", err, es)
531 | end
532 | msg.error(err)
533 | return
534 | end
535 |
536 | local json, err = utils.parse_json(json)
537 |
538 | if (json == nil) then
539 | msg.error("failed to parse JSON data: " .. err)
540 | return
541 | end
542 |
543 | msg.verbose("youtube-dl succeeded!")
544 | msg.debug('ytdl parsing took '..os.clock()-start_time..' seconds')
545 |
546 | json["proxy"] = json["proxy"] or proxy
547 |
548 | -- what did we get?
549 | if json["direct"] then
550 | -- direct URL, nothing to do
551 | msg.verbose("Got direct URL")
552 | return
553 | elseif (json["_type"] == "playlist")
554 | or (json["_type"] == "multi_video") then
555 | -- a playlist
556 |
557 | if (#json.entries == 0) then
558 | msg.warn("Got empty playlist, nothing to play.")
559 | return
560 | end
561 |
562 | local self_redirecting_url =
563 | json.entries[1]["_type"] ~= "url_transparent" and
564 | json.entries[1]["webpage_url"] and
565 | json.entries[1]["webpage_url"] == json["webpage_url"]
566 |
567 |
568 | -- some funky guessing to detect multi-arc videos
569 | if self_redirecting_url and #json.entries > 1
570 | and json.entries[1].protocol == "m3u8_native"
571 | and json.entries[1].url then
572 | msg.verbose("multi-arc video detected, building EDL")
573 |
574 | local playlist = edl_track_joined(json.entries)
575 |
576 | msg.debug("EDL: " .. playlist)
577 |
578 | if not playlist then
579 | return
580 | end
581 |
582 | -- can't change the http headers for each entry, so use the 1st
583 | set_http_headers(json.entries[1].http_headers)
584 |
585 | mp.set_property("stream-open-filename", playlist)
586 | if not (json.title == nil) then
587 | mp.set_property("file-local-options/force-media-title",
588 | json.title)
589 | end
590 |
591 | -- there might not be subs for the first segment
592 | local entry_wsubs = nil
593 | for i, entry in pairs(json.entries) do
594 | if not (entry.requested_subtitles == nil) then
595 | entry_wsubs = i
596 | break
597 | end
598 | end
599 |
600 | if not (entry_wsubs == nil) and
601 | not (json.entries[entry_wsubs].duration == nil) then
602 | for j, req in pairs(json.entries[entry_wsubs].requested_subtitles) do
603 | local subfile = "edl://"
604 | for i, entry in pairs(json.entries) do
605 | if not (entry.requested_subtitles == nil) and
606 | not (entry.requested_subtitles[j] == nil) and
607 | url_is_safe(entry.requested_subtitles[j].url) then
608 | subfile = subfile..edl_escape(entry.requested_subtitles[j].url)
609 | else
610 | subfile = subfile..edl_escape("memory://WEBVTT")
611 | end
612 | subfile = subfile..",length="..entry.duration..";"
613 | end
614 | msg.debug(j.." sub EDL: "..subfile)
615 | mp.commandv("sub-add", subfile, "auto", req.ext, j)
616 | end
617 | end
618 |
619 | elseif self_redirecting_url and #json.entries == 1 then
620 | msg.verbose("Playlist with single entry detected.")
621 | add_single_video(json.entries[1])
622 | else
623 | local playlist_index = parse_yt_playlist(url, json)
624 | local playlist = {"#EXTM3U"}
625 | for i, entry in pairs(json.entries) do
626 | local site = entry.url
627 | local title = entry.title
628 |
629 | if not (title == nil) then
630 | title = string.gsub(title, '%s+', ' ')
631 | table.insert(playlist, "#EXTINF:0," .. title)
632 | end
633 |
634 | --[[ some extractors will still return the full info for
635 | all clips in the playlist and the URL will point
636 | directly to the file in that case, which we don't
637 | want so get the webpage URL instead, which is what
638 | we want, but only if we aren't going to trigger an
639 | infinite loop
640 | --]]
641 | if entry["webpage_url"] and not self_redirecting_url then
642 | site = entry["webpage_url"]
643 | end
644 |
645 | -- links without protocol as returned by --flat-playlist
646 | if not site:find("://") then
647 | -- youtube extractor provides only IDs,
648 | -- others come prefixed with the extractor name and ":"
649 | local prefix = site:find(":") and "ytdl://" or
650 | "https://youtu.be/"
651 | table.insert(playlist, prefix .. site)
652 | elseif url_is_safe(site) then
653 | table.insert(playlist, site)
654 | end
655 |
656 | end
657 |
658 | if use_playlist and
659 | not option_was_set("playlist-start") and playlist_index then
660 | mp.set_property_number("playlist-start", playlist_index)
661 | end
662 |
663 | mp.set_property("stream-open-filename", "memory://" .. table.concat(playlist, "\n"))
664 |
665 | -- This disables mpv's mushy playlist security code, which will
666 | -- break links that will be resolved to EDL later (because EDL is
667 | -- not considered "safe", and the playlist entries got tagged as
668 | -- network originating due to the playlist redirection).
669 | mp.set_property_native("file-local-options/load-unsafe-playlists", true)
670 | end
671 |
672 | else -- probably a video
673 | add_single_video(json)
674 | end
675 | msg.debug('script running time: '..os.clock()-start_time..' seconds')
676 | end
677 |
678 | if (not o.try_ytdl_first) then
679 | mp.add_hook("on_load", 10, function ()
680 | msg.verbose('ytdl:// hook')
681 | local url = mp.get_property("stream-open-filename", "")
682 | if not (url:find("ytdl://") == 1) then
683 | msg.verbose('not a ytdl:// url')
684 | return
685 | end
686 | run_ytdl_hook(url)
687 | end)
688 | end
689 |
690 | mp.add_hook(o.try_ytdl_first and "on_load" or "on_load_fail", 10, function()
691 | msg.verbose('full hook')
692 | local url = mp.get_property("stream-open-filename", "")
693 | if not (url:find("ytdl://") == 1) and
694 | not ((url:find("https?://") == 1) and not is_blacklisted(url)) then
695 | return
696 | end
697 | run_ytdl_hook(url)
698 | end)
699 |
700 | mp.add_hook("on_preloaded", 10, function ()
701 | if next(chapter_list) ~= nil then
702 | msg.verbose("Setting chapters")
703 |
704 | mp.set_property_native("chapter-list", chapter_list)
705 | chapter_list = {}
706 | end
707 | end)
708 |
--------------------------------------------------------------------------------
/src/Mpv.NET.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 15
4 | VisualStudioVersion = 15.0.27703.2026
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mpv.NET", "Mpv.NET\Mpv.NET.csproj", "{02D1E0D1-D9C6-4E2A-A598-7ECA726B7166}"
7 | EndProject
8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mpv.NET.WPFExample", "Mpv.NET.WPFExample\Mpv.NET.WPFExample.csproj", "{8FE66FA3-7AC7-474E-B0F9-A052EACDD62C}"
9 | EndProject
10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mpv.NET.WinFormsExample", "Mpv.NET.WinFormsExample\Mpv.NET.WinFormsExample.csproj", "{359B1FAB-E90E-40E3-A285-BCE50F671E14}"
11 | EndProject
12 | Global
13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
14 | Debug|Any CPU = Debug|Any CPU
15 | Release|Any CPU = Release|Any CPU
16 | EndGlobalSection
17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
18 | {02D1E0D1-D9C6-4E2A-A598-7ECA726B7166}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
19 | {02D1E0D1-D9C6-4E2A-A598-7ECA726B7166}.Debug|Any CPU.Build.0 = Debug|Any CPU
20 | {02D1E0D1-D9C6-4E2A-A598-7ECA726B7166}.Release|Any CPU.ActiveCfg = Release|Any CPU
21 | {02D1E0D1-D9C6-4E2A-A598-7ECA726B7166}.Release|Any CPU.Build.0 = Release|Any CPU
22 | {8FE66FA3-7AC7-474E-B0F9-A052EACDD62C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
23 | {8FE66FA3-7AC7-474E-B0F9-A052EACDD62C}.Debug|Any CPU.Build.0 = Debug|Any CPU
24 | {8FE66FA3-7AC7-474E-B0F9-A052EACDD62C}.Release|Any CPU.ActiveCfg = Release|Any CPU
25 | {8FE66FA3-7AC7-474E-B0F9-A052EACDD62C}.Release|Any CPU.Build.0 = Release|Any CPU
26 | {359B1FAB-E90E-40E3-A285-BCE50F671E14}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
27 | {359B1FAB-E90E-40E3-A285-BCE50F671E14}.Debug|Any CPU.Build.0 = Debug|Any CPU
28 | {359B1FAB-E90E-40E3-A285-BCE50F671E14}.Release|Any CPU.ActiveCfg = Release|Any CPU
29 | {359B1FAB-E90E-40E3-A285-BCE50F671E14}.Release|Any CPU.Build.0 = Release|Any CPU
30 | EndGlobalSection
31 | GlobalSection(SolutionProperties) = preSolution
32 | HideSolutionNode = FALSE
33 | EndGlobalSection
34 | GlobalSection(ExtensibilityGlobals) = postSolution
35 | SolutionGuid = {488BB195-3C57-4720-9EA1-9CDAAE260B2C}
36 | EndGlobalSection
37 | EndGlobal
38 |
--------------------------------------------------------------------------------
/src/Mpv.NET/API/Enums/MpvEndFileReason.cs:
--------------------------------------------------------------------------------
1 | namespace Mpv.NET.API
2 | {
3 | public enum MpvEndFileReason
4 | {
5 | EndOfFile = 0,
6 | Stop = 2,
7 | Quit = 3,
8 | Error = 4,
9 | Redirect = 5
10 | }
11 | }
--------------------------------------------------------------------------------
/src/Mpv.NET/API/Enums/MpvError.cs:
--------------------------------------------------------------------------------
1 | namespace Mpv.NET.API
2 | {
3 | public enum MpvError
4 | {
5 | Success = 0,
6 | EventQueueFull = -1,
7 | NoMem = -2,
8 | Uninitialised = -3,
9 | InvalidParameter = -4,
10 | OptionNotFound = -5,
11 | OptionFormat = -6,
12 | OptionError = -7,
13 | PropertyNotFound = -8,
14 | PropertyFormat = -9,
15 | PropertyUnavailable = -10,
16 | PropertyError = -11,
17 | Command = -12,
18 | LoadingFailed = -13,
19 | AoInitFailed = -14,
20 | VoInitFailed = -15,
21 | NothingToPlay = -16,
22 | UnknownFormat = -17,
23 | Unsupported = -18,
24 | NotImplemented = -19,
25 | Generic = -20
26 | }
27 | }
--------------------------------------------------------------------------------
/src/Mpv.NET/API/Enums/MpvEventID.cs:
--------------------------------------------------------------------------------
1 | namespace Mpv.NET.API
2 | {
3 | public enum MpvEventID
4 | {
5 | None = 0,
6 | Shutdown = 1,
7 | LogMessage = 2,
8 | GetPropertyReply = 3,
9 | SetPropertyReply = 4,
10 | CommandReply = 5,
11 | StartFile = 6,
12 | EndFile = 7,
13 | FileLoaded = 8,
14 | TracksChanged = 9,
15 | TrackSwitched = 10,
16 | Idle = 11,
17 | Pause = 12,
18 | Unpause = 13,
19 | Tick = 14,
20 | ScriptInputDispatch = 15,
21 | ClientMessage = 16,
22 | VideoReconfig = 17,
23 | AudioReconfig = 18,
24 | MetadataUpdate = 19,
25 | Seek = 20,
26 | PlaybackRestart = 21,
27 | PropertyChange = 22,
28 | ChapterChange = 23,
29 | QueueOverflow = 24
30 | }
31 | }
--------------------------------------------------------------------------------
/src/Mpv.NET/API/Enums/MpvFormat.cs:
--------------------------------------------------------------------------------
1 | namespace Mpv.NET.API
2 | {
3 | public enum MpvFormat
4 | {
5 | None = 0,
6 | String = 1,
7 | OsdString = 2,
8 | Flag = 3,
9 | Int64 = 4,
10 | Double = 5,
11 | Node = 6,
12 | NodeArray = 7,
13 | NodeMap = 8,
14 | ByteArray = 9
15 | }
16 | }
--------------------------------------------------------------------------------
/src/Mpv.NET/API/Enums/MpvLogLevel.cs:
--------------------------------------------------------------------------------
1 | namespace Mpv.NET.API
2 | {
3 | public enum MpvLogLevel
4 | {
5 | None = 0,
6 | Fatal = 10,
7 | Error = 20,
8 | Warning = 30,
9 | Info = 40,
10 | V = 50,
11 | Debug = 60,
12 | Trace = 70
13 | }
14 | }
--------------------------------------------------------------------------------
/src/Mpv.NET/API/EventArgs/MpvClientMessageEventArgs.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Mpv.NET.API
4 | {
5 | public class MpvClientMessageEventArgs : EventArgs
6 | {
7 | public MpvEventClientMessage EventClientMessage { get; private set; }
8 |
9 | public MpvClientMessageEventArgs(MpvEventClientMessage eventClientMessage)
10 | {
11 | EventClientMessage = eventClientMessage;
12 | }
13 | }
14 | }
--------------------------------------------------------------------------------
/src/Mpv.NET/API/EventArgs/MpvCommandReplyEventArgs.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Mpv.NET.API
4 | {
5 | public class MpvCommandReplyEventArgs : EventArgs
6 | {
7 | public ulong ReplyUserData { get; private set; }
8 |
9 | public MpvError Error { get; private set; }
10 |
11 | public MpvCommandReplyEventArgs(ulong replyUserData, MpvError error)
12 | {
13 | ReplyUserData = replyUserData;
14 | Error = error;
15 | }
16 | }
17 | }
--------------------------------------------------------------------------------
/src/Mpv.NET/API/EventArgs/MpvEndFileEventArgs.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Mpv.NET.API
4 | {
5 | public class MpvEndFileEventArgs : EventArgs
6 | {
7 | public MpvEventEndFile EventEndFile { get; private set; }
8 |
9 | public MpvEndFileEventArgs(MpvEventEndFile eventEndFile)
10 | {
11 | EventEndFile = eventEndFile;
12 | }
13 | }
14 | }
--------------------------------------------------------------------------------
/src/Mpv.NET/API/EventArgs/MpvGetPropertyReplyEventArgs.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Mpv.NET.API
4 | {
5 | public class MpvGetPropertyReplyEventArgs : EventArgs
6 | {
7 | public ulong ReplyUserData { get; private set; }
8 |
9 | public MpvError Error { get; private set; }
10 |
11 | public MpvEventProperty EventProperty { get; private set; }
12 |
13 | public MpvGetPropertyReplyEventArgs(ulong replyUserData, MpvError error, MpvEventProperty eventProperty)
14 | {
15 | ReplyUserData = replyUserData;
16 | Error = error;
17 | EventProperty = eventProperty;
18 | }
19 | }
20 | }
--------------------------------------------------------------------------------
/src/Mpv.NET/API/EventArgs/MpvLogMessageEventArgs.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Mpv.NET.API
4 | {
5 | public class MpvLogMessageEventArgs : EventArgs
6 | {
7 | public MpvLogMessage Message { get; private set; }
8 |
9 | public MpvLogMessageEventArgs(MpvLogMessage message)
10 | {
11 | Message = message;
12 | }
13 | }
14 | }
--------------------------------------------------------------------------------
/src/Mpv.NET/API/EventArgs/MpvPropertyChangeEventArgs.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Mpv.NET.API
4 | {
5 | public class MpvPropertyChangeEventArgs : EventArgs
6 | {
7 | public ulong ReplyUserData { get; private set; }
8 |
9 | public MpvEventProperty EventProperty { get; private set; }
10 |
11 | public MpvPropertyChangeEventArgs(ulong replyUserData, MpvEventProperty eventProperty)
12 | {
13 | ReplyUserData = replyUserData;
14 | EventProperty = eventProperty;
15 | }
16 | }
17 | }
--------------------------------------------------------------------------------
/src/Mpv.NET/API/EventArgs/MpvSetPropertyReplyEventArgs.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Mpv.NET.API
4 | {
5 | public class MpvSetPropertyReplyEventArgs : EventArgs
6 | {
7 | public ulong ReplyUserData { get; private set; }
8 |
9 | public MpvError Error { get; private set; }
10 |
11 | public MpvSetPropertyReplyEventArgs(ulong replyUserData, MpvError error)
12 | {
13 | this.ReplyUserData = replyUserData;
14 | Error = error;
15 | }
16 | }
17 | }
--------------------------------------------------------------------------------
/src/Mpv.NET/API/Exceptions/MpvAPIException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Mpv.NET.API
4 | {
5 | public class MpvAPIException : Exception
6 | {
7 | public MpvError Error { get; private set; }
8 |
9 | public static MpvAPIException FromError(MpvError error, IMpvFunctions functions)
10 | {
11 | var errorString = functions.ErrorString(error);
12 |
13 | var message = $"Error occured: \"{errorString}\".";
14 |
15 | return new MpvAPIException(message, error);
16 | }
17 |
18 | public MpvAPIException(string message, MpvError error) : base(message)
19 | {
20 | Error = error;
21 | }
22 |
23 | public MpvAPIException(string message) : base(message)
24 | {
25 | }
26 | }
27 | }
--------------------------------------------------------------------------------
/src/Mpv.NET/API/Helpers/MpvLogLevelHelper.cs:
--------------------------------------------------------------------------------
1 | namespace Mpv.NET.API
2 | {
3 | public static class MpvLogLevelHelper
4 | {
5 | public static string GetStringForLogLevel(MpvLogLevel logLevel)
6 | {
7 | switch (logLevel)
8 | {
9 | case MpvLogLevel.None:
10 | return "no";
11 | case MpvLogLevel.Fatal:
12 | return "fatal";
13 | case MpvLogLevel.Error:
14 | return "error";
15 | case MpvLogLevel.Warning:
16 | return "warn";
17 | case MpvLogLevel.Info:
18 | return "info";
19 | case MpvLogLevel.V:
20 | return "v";
21 | case MpvLogLevel.Debug:
22 | return "debug";
23 | case MpvLogLevel.Trace:
24 | return "trace";
25 | default:
26 | return null;
27 | }
28 | }
29 | }
30 | }
--------------------------------------------------------------------------------
/src/Mpv.NET/API/IMpvEventLoop.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Mpv.NET.API
4 | {
5 | public interface IMpvEventLoop
6 | {
7 | bool IsRunning { get; }
8 |
9 | Action Callback { get; set; }
10 |
11 | IMpvFunctions Functions { get; set; }
12 |
13 | void Start();
14 | void Stop();
15 | }
16 | }
--------------------------------------------------------------------------------
/src/Mpv.NET/API/IMpvFunctions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Mpv.NET.API
4 | {
5 | public interface IMpvFunctions
6 | {
7 | MpvClientAPIVersion ClientAPIVersion { get; }
8 | MpvErrorString ErrorString { get; }
9 | MpvFree Free { get; }
10 | MpvClientName ClientName { get; }
11 | MpvCreate Create { get; }
12 | MpvInitialise Initialise { get; }
13 | MpvDetachDestroy DetachDestroy { get; }
14 | MpvTerminateDestroy TerminateDestroy { get; }
15 | MpvCreateClient CreateClient { get; }
16 | MpvLoadConfigFile LoadConfigFile { get; }
17 | MpvGetTimeUs GetTimeUs { get; }
18 | MpvSetOption SetOption { get; }
19 | MpvSetOptionString SetOptionString { get; }
20 | MpvCommand Command { get; }
21 | MpvCommandAsync CommandAsync { get; }
22 | MpvSetProperty SetProperty { get; }
23 | MpvSetPropertyString SetPropertyString { get; }
24 | MpvSetPropertyAsync SetPropertyAsync { get; }
25 | MpvGetProperty GetProperty { get; }
26 | MpvGetPropertyString GetPropertyString { get; }
27 | MpvGetPropertyOSDString GetPropertyOSDString { get; }
28 | MpvGetPropertyAsync GetPropertyAsync { get; }
29 | MpvObserveProperty ObserveProperty { get; }
30 | MpvUnobserveProperty UnobserveProperty { get; }
31 | MpvEventName EventName { get; }
32 | MpvRequestEvent RequestEvent { get; }
33 | MpvRequestLogMessages RequestLogMessages { get; }
34 | MpvWaitEvent WaitEvent { get; }
35 | MpvWakeup Wakeup { get; }
36 | MpvSetWakeupCallback SetWakeupCallback { get; }
37 | MpvGetWakeupPipe GetWakeupPipe { get; }
38 | MpvWaitAsyncRequests WaitAsyncRequests { get; }
39 |
40 | MpvGetPropertyDouble GetPropertyDouble { get; }
41 | MpvGetPropertyLong GetPropertyLong { get; }
42 | }
43 | }
--------------------------------------------------------------------------------
/src/Mpv.NET/API/Interop/Mpv/MpvMarshal.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Runtime.InteropServices;
4 | using System.Text;
5 |
6 | namespace Mpv.NET.API.Interop
7 | {
8 | public static class MpvMarshal
9 | {
10 | public static IntPtr GetComPtrFromManagedUTF8String(string @string)
11 | {
12 | Guard.AgainstNull(@string, nameof(@string));
13 |
14 | @string += '\0';
15 |
16 | var stringBytes = Encoding.UTF8.GetBytes(@string);
17 | var stringBytesCount = stringBytes.Length;
18 |
19 | var stringPtr = Marshal.AllocCoTaskMem(stringBytesCount);
20 | Marshal.Copy(stringBytes, 0, stringPtr, stringBytesCount);
21 |
22 | return stringPtr;
23 | }
24 |
25 | public static string GetManagedUTF8StringFromPtr(IntPtr stringPtr)
26 | {
27 | if (stringPtr == IntPtr.Zero)
28 | throw new ArgumentException("Cannot get string from invalid pointer.");
29 |
30 | var stringBytes = new List();
31 | var offset = 0;
32 |
33 | // Just to be safe!
34 | while (offset < short.MaxValue)
35 | {
36 | var @byte = Marshal.ReadByte(stringPtr, offset);
37 | if (@byte == '\0')
38 | break;
39 |
40 | stringBytes.Add(@byte);
41 |
42 | offset++;
43 | }
44 |
45 | var stringBytesArray = stringBytes.ToArray();
46 |
47 | return Encoding.UTF8.GetString(stringBytesArray);
48 | }
49 |
50 | public static IntPtr GetComPtrForManagedUTF8StringArray(string[] inArray, out IntPtr[] outArray)
51 | {
52 | Guard.AgainstNull(inArray, nameof(inArray));
53 |
54 | var numberOfStrings = inArray.Length + 1;
55 |
56 | outArray = new IntPtr[numberOfStrings];
57 |
58 | // Allocate COM memory since this array will be passed to
59 | // a C function. This allocates space for the pointers that will point
60 | // to each string.
61 | var rootPointer = Marshal.AllocCoTaskMem(IntPtr.Size * numberOfStrings);
62 |
63 | for (var index = 0; index < inArray.Length; index++)
64 | {
65 | var currentString = inArray[index];
66 | var currentStringPtr = GetComPtrFromManagedUTF8String(currentString);
67 |
68 | outArray[index] = currentStringPtr;
69 | }
70 |
71 | Marshal.Copy(outArray, 0, rootPointer, numberOfStrings);
72 |
73 | return rootPointer;
74 | }
75 |
76 | public static void FreeComPtrArray(IntPtr[] ptrArray)
77 | {
78 | Guard.AgainstNull(ptrArray, nameof(ptrArray));
79 |
80 | foreach (var intPtr in ptrArray)
81 | Marshal.FreeCoTaskMem(intPtr);
82 | }
83 |
84 | public static TStruct PtrToStructure(IntPtr ptr) where TStruct : struct
85 | {
86 | if (ptr == IntPtr.Zero)
87 | throw new ArgumentException("Invalid pointer.");
88 |
89 | return (TStruct)Marshal.PtrToStructure(ptr, typeof(TStruct));
90 | }
91 |
92 | public static TDelegate LoadUnmanagedFunction(IntPtr dllHandle, string functionName) where TDelegate : class
93 | {
94 | if (dllHandle == IntPtr.Zero)
95 | throw new ArgumentException("DLL handle is invalid.", nameof(dllHandle));
96 |
97 | Guard.AgainstNullOrEmptyOrWhiteSpaceString(functionName, nameof(functionName));
98 |
99 | var functionPtr = WinFunctions.GetProcAddress(dllHandle, functionName);
100 | if (functionPtr == IntPtr.Zero)
101 | return null;
102 |
103 | return (TDelegate)(object)Marshal.GetDelegateForFunctionPointer(functionPtr, typeof(TDelegate));
104 | }
105 | }
106 | }
--------------------------------------------------------------------------------
/src/Mpv.NET/API/Interop/Mpv/MpvStringMarshaler.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Runtime.InteropServices;
3 |
4 | namespace Mpv.NET.API.Interop
5 | {
6 | internal class MpvStringMarshaler : ICustomMarshaler
7 | {
8 | private MarshalerCleanUpMethod CleanUpMethod { get; set; }
9 |
10 | public MpvStringMarshaler(MarshalerCleanUpMethod cleanUpMethod)
11 | {
12 | CleanUpMethod = cleanUpMethod;
13 | }
14 |
15 | public static ICustomMarshaler GetInstance(string cookie)
16 | {
17 | var cleanUpMethod = MarshalerCleanUpMethod.None;
18 | switch (cookie)
19 | {
20 | case "free-global":
21 | cleanUpMethod = MarshalerCleanUpMethod.Global;
22 | break;
23 | case "free-com":
24 | cleanUpMethod = MarshalerCleanUpMethod.Com;
25 | break;
26 | }
27 |
28 | return new MpvStringMarshaler(cleanUpMethod);
29 | }
30 |
31 | public void CleanUpManagedData(object ManagedObj)
32 | {
33 | }
34 |
35 | public void CleanUpNativeData(IntPtr pNativeData)
36 | {
37 | switch (CleanUpMethod)
38 | {
39 | case MarshalerCleanUpMethod.Com:
40 | Marshal.FreeCoTaskMem(pNativeData);
41 | break;
42 | case MarshalerCleanUpMethod.Global:
43 | Marshal.FreeHGlobal(pNativeData);
44 | break;
45 | }
46 | }
47 |
48 | public int GetNativeDataSize()
49 | {
50 | throw new NotImplementedException();
51 | }
52 |
53 | public IntPtr MarshalManagedToNative(object managedObj)
54 | {
55 | if (!(managedObj is string @string))
56 | return IntPtr.Zero;
57 |
58 | var stringPtr = MpvMarshal.GetComPtrFromManagedUTF8String(@string);
59 |
60 | return stringPtr;
61 | }
62 |
63 | public object MarshalNativeToManaged(IntPtr pNativeData)
64 | {
65 | if (pNativeData == IntPtr.Zero)
66 | return null;
67 |
68 | var @string = MpvMarshal.GetManagedUTF8StringFromPtr(pNativeData);
69 |
70 | return @string;
71 | }
72 | }
73 |
74 | internal enum MarshalerCleanUpMethod
75 | {
76 | None,
77 | Com,
78 | Global
79 | }
80 | }
--------------------------------------------------------------------------------
/src/Mpv.NET/API/Interop/Windows/WinFunctions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Runtime.InteropServices;
3 |
4 | namespace Mpv.NET.API.Interop
5 | {
6 | internal static class WinFunctions
7 | {
8 | // https://docs.microsoft.com/en-us/windows/desktop/api/libloaderapi/nf-libloaderapi-loadlibrarya
9 | [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Ansi, BestFitMapping = false)]
10 | public static extern IntPtr LoadLibrary(string lpFileName);
11 |
12 | // https://docs.microsoft.com/en-us/windows/desktop/api/libloaderapi/nf-libloaderapi-freelibrary
13 | [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Ansi, BestFitMapping = false)]
14 | public static extern int FreeLibrary(IntPtr hModule);
15 |
16 | // https://docs.microsoft.com/en-us/windows/desktop/api/libloaderapi/nf-libloaderapi-getprocaddress
17 | [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Ansi, BestFitMapping = false)]
18 | public static extern IntPtr GetProcAddress(IntPtr hModule, string lProcName);
19 | }
20 | }
--------------------------------------------------------------------------------
/src/Mpv.NET/API/Mpv.cs:
--------------------------------------------------------------------------------
1 | using Mpv.NET.API.Interop;
2 | using System;
3 | using System.IO;
4 | using System.Runtime.InteropServices;
5 |
6 | namespace Mpv.NET.API
7 | {
8 | public partial class Mpv : IDisposable
9 | {
10 | public IMpvFunctions Functions
11 | {
12 | get => functions;
13 | set
14 | {
15 | Guard.AgainstNull(value);
16 |
17 | functions = value;
18 | }
19 | }
20 |
21 | public IMpvEventLoop EventLoop
22 | {
23 | get => eventLoop;
24 | set
25 | {
26 | Guard.AgainstNull(value);
27 |
28 | if (!value.IsRunning)
29 | value.Start();
30 |
31 | eventLoop = value;
32 | }
33 | }
34 |
35 | public IntPtr Handle
36 | {
37 | get => handle;
38 | private set
39 | {
40 | if (value == IntPtr.Zero)
41 | throw new ArgumentException("Invalid handle pointer.", nameof(handle));
42 |
43 | handle = value;
44 | }
45 | }
46 |
47 | private IMpvFunctions functions;
48 | private IMpvEventLoop eventLoop;
49 | private IntPtr handle;
50 |
51 | private bool disposed = false;
52 |
53 | public Mpv(string dllPath)
54 | {
55 | Guard.AgainstNullOrEmptyOrWhiteSpaceString(dllPath, nameof(dllPath));
56 |
57 | Functions = new MpvFunctions(dllPath);
58 |
59 | InitialiseMpv();
60 |
61 | eventLoop = new MpvEventLoop(EventCallback, Handle, Functions);
62 | eventLoop.Start();
63 | }
64 |
65 | public Mpv(IMpvFunctions functions)
66 | {
67 | Functions = functions;
68 |
69 | InitialiseMpv();
70 |
71 | eventLoop = new MpvEventLoop(EventCallback, Handle, Functions);
72 | }
73 |
74 | public Mpv(IMpvFunctions functions, IMpvEventLoop eventLoop)
75 | {
76 | Functions = functions;
77 |
78 | EventLoop = eventLoop;
79 |
80 | InitialiseMpv();
81 | }
82 |
83 | internal Mpv(IntPtr handle, IMpvFunctions functions)
84 | {
85 | Handle = handle;
86 |
87 | Functions = functions;
88 |
89 | eventLoop = new MpvEventLoop(EventCallback, Handle, Functions);
90 | eventLoop.Start();
91 | }
92 |
93 | private void InitialiseMpv()
94 | {
95 | Handle = Functions.Create();
96 | if (Handle == IntPtr.Zero)
97 | throw new MpvAPIException("Failed to create Mpv context.");
98 |
99 | var error = Functions.Initialise(Handle);
100 | if (error != MpvError.Success)
101 | throw MpvAPIException.FromError(error, Functions);
102 | }
103 |
104 | public long ClientAPIVersion()
105 | {
106 | Guard.AgainstDisposed(disposed, nameof(Mpv));
107 |
108 | return Functions.ClientAPIVersion();
109 | }
110 |
111 | public string ErrorString(MpvError error)
112 | {
113 | Guard.AgainstDisposed(disposed, nameof(Mpv));
114 |
115 | return Functions.ErrorString(error);
116 | }
117 |
118 | public string ClientName()
119 | {
120 | Guard.AgainstDisposed(disposed, nameof(Mpv));
121 |
122 | return Functions.ClientName(Handle);
123 | }
124 |
125 | public Mpv CreateClient()
126 | {
127 | Guard.AgainstDisposed(disposed, nameof(Mpv));
128 |
129 | var newHandle = Functions.CreateClient(Handle, out string name);
130 | if (newHandle == IntPtr.Zero)
131 | throw new MpvAPIException("Failed to create new client.");
132 |
133 | return new Mpv(newHandle, Functions);
134 | }
135 |
136 | public void LoadConfigFile(string absolutePath)
137 | {
138 | Guard.AgainstDisposed(disposed, nameof(Mpv));
139 |
140 | if (!Uri.TryCreate(absolutePath, UriKind.Absolute, out Uri _))
141 | throw new ArgumentException("Path is not absolute.");
142 |
143 | if (!File.Exists(absolutePath))
144 | throw new FileNotFoundException("Config file not found.");
145 |
146 | var error = Functions.LoadConfigFile(Handle, absolutePath);
147 | if (error != MpvError.Success)
148 | throw MpvAPIException.FromError(error, functions);
149 | }
150 |
151 | public long GetTimeUs()
152 | {
153 | Guard.AgainstDisposed(disposed, nameof(Mpv));
154 |
155 | return Functions.GetTimeUs(Handle);
156 | }
157 |
158 | [Obsolete("Semi-deprecated in favour of SetProperty. Very few options still need to be set via SetOption.")]
159 | public void SetOption(string name, byte[] data, MpvFormat format = MpvFormat.ByteArray)
160 | {
161 | Guard.AgainstDisposed(disposed, nameof(Mpv));
162 | Guard.AgainstNullOrEmptyOrWhiteSpaceString(name, nameof(name));
163 | Guard.AgainstNull(data, nameof(data));
164 |
165 | var dataLength = data.Length;
166 | var dataPtr = Marshal.AllocCoTaskMem(dataLength);
167 |
168 | try
169 | {
170 | Marshal.Copy(data, 0, dataPtr, dataLength);
171 |
172 | var error = Functions.SetOption(Handle, name, format, dataPtr);
173 | if (error != MpvError.Success)
174 | throw MpvAPIException.FromError(error, Functions);
175 | }
176 | finally
177 | {
178 | Marshal.FreeCoTaskMem(dataPtr);
179 | }
180 | }
181 |
182 | [Obsolete("Semi-deprecated in favour of SetPropertyString. Very few options still need to be set via SetOptionString.")]
183 | public void SetOptionString(string name, string data)
184 | {
185 | Guard.AgainstDisposed(disposed, nameof(Mpv));
186 | Guard.AgainstNullOrEmptyOrWhiteSpaceString(name, nameof(name));
187 | Guard.AgainstNull(data, nameof(data));
188 |
189 | var error = Functions.SetOptionString(Handle, name, data);
190 | if (error != MpvError.Success)
191 | throw MpvAPIException.FromError(error, Functions);
192 | }
193 |
194 | public void Command(params string[] args)
195 | {
196 | Guard.AgainstDisposed(disposed, nameof(Mpv));
197 | Guard.AgainstNull(args, nameof(args));
198 |
199 | if (args.Length < 1)
200 | throw new ArgumentException("Missing arguments.", nameof(args));
201 |
202 | var argsPtr = MpvMarshal.GetComPtrForManagedUTF8StringArray(args, out IntPtr[] argsPtrs);
203 | if (argsPtr == IntPtr.Zero)
204 | throw new MpvAPIException("Failed to convert string array to pointer array.");
205 |
206 | try
207 | {
208 | var error = Functions.Command(Handle, argsPtr);
209 | if (error != MpvError.Success)
210 | throw MpvAPIException.FromError(error, Functions);
211 | }
212 | finally
213 | {
214 | MpvMarshal.FreeComPtrArray(argsPtrs);
215 | Marshal.FreeCoTaskMem(argsPtr);
216 | }
217 | }
218 |
219 | public void CommandAsync(ulong replyUserData, params string[] args)
220 | {
221 | Guard.AgainstDisposed(disposed, nameof(Mpv));
222 | Guard.AgainstNull(args, nameof(args));
223 |
224 | if (args.Length < 1)
225 | throw new ArgumentException("Missing arguments.", nameof(args));
226 |
227 | var argsPtr = MpvMarshal.GetComPtrForManagedUTF8StringArray(args, out IntPtr[] argsPtrs);
228 | if (argsPtr == IntPtr.Zero)
229 | throw new MpvAPIException("Failed to convert string array to pointer array.");
230 |
231 | try
232 | {
233 | var error = Functions.CommandAsync(Handle, replyUserData, argsPtr);
234 | if (error != MpvError.Success)
235 | throw MpvAPIException.FromError(error, Functions);
236 | }
237 | finally
238 | {
239 | MpvMarshal.FreeComPtrArray(argsPtrs);
240 | Marshal.FreeCoTaskMem(argsPtr);
241 | }
242 | }
243 |
244 | public void SetProperty(string name, byte[] data, MpvFormat format = MpvFormat.ByteArray)
245 | {
246 | Guard.AgainstDisposed(disposed, nameof(Mpv));
247 | Guard.AgainstNullOrEmptyOrWhiteSpaceString(name, nameof(name));
248 | Guard.AgainstNull(data, nameof(data));
249 |
250 | if (data.Length < 1)
251 | throw new ArgumentException("Data is empty.", nameof(data));
252 |
253 | var dataLength = data.Length;
254 | var dataPtr = Marshal.AllocCoTaskMem(dataLength);
255 |
256 | try
257 | {
258 | Marshal.Copy(data, 0, dataPtr, dataLength);
259 |
260 | var error = Functions.SetProperty(Handle, name, format, dataPtr);
261 | if (error != MpvError.Success)
262 | throw MpvAPIException.FromError(error, Functions);
263 | }
264 | finally
265 | {
266 | Marshal.FreeCoTaskMem(dataPtr);
267 | }
268 | }
269 |
270 | public void SetPropertyString(string name, string value)
271 | {
272 | Guard.AgainstDisposed(disposed, nameof(Mpv));
273 | Guard.AgainstNullOrEmptyOrWhiteSpaceString(name, nameof(name));
274 | Guard.AgainstNullOrEmptyOrWhiteSpaceString(value, nameof(value));
275 |
276 | var error = Functions.SetPropertyString(Handle, name, value);
277 | if (error != MpvError.Success)
278 | throw MpvAPIException.FromError(error, Functions);
279 | }
280 |
281 | public void SetPropertyAsync(string name, byte[] data, ulong replyUserData, MpvFormat format = MpvFormat.ByteArray)
282 | {
283 | Guard.AgainstDisposed(disposed, nameof(Mpv));
284 | Guard.AgainstNullOrEmptyOrWhiteSpaceString(name, nameof(name));
285 | Guard.AgainstNull(data, nameof(data));
286 |
287 | if (data.Length < 1)
288 | throw new ArgumentException("Data is empty.", nameof(data));
289 |
290 | var dataLength = data.Length;
291 | var dataPtr = Marshal.AllocCoTaskMem(dataLength);
292 |
293 | try
294 | {
295 | Marshal.Copy(data, 0, dataPtr, dataLength);
296 |
297 | var error = Functions.SetPropertyAsync(Handle, replyUserData, name, format, dataPtr);
298 | if (error != MpvError.Success)
299 | throw MpvAPIException.FromError(error, Functions);
300 | }
301 | finally
302 | {
303 | Marshal.FreeCoTaskMem(dataPtr);
304 | }
305 | }
306 |
307 | public void SetPropertyLong(string name, long data)
308 | {
309 | Guard.AgainstDisposed(disposed, nameof(Mpv));
310 | Guard.AgainstNullOrEmptyOrWhiteSpaceString(name, nameof(name));
311 |
312 | var dataBytes = BitConverter.GetBytes(data);
313 | SetProperty(name, dataBytes, MpvFormat.Int64);
314 | }
315 |
316 | public void SetPropertyDouble(string name, double data)
317 | {
318 | Guard.AgainstDisposed(disposed, nameof(Mpv));
319 | Guard.AgainstNullOrEmptyOrWhiteSpaceString(name, nameof(name));
320 |
321 | var dataBytes = BitConverter.GetBytes(data);
322 | SetProperty(name, dataBytes, MpvFormat.Double);
323 | }
324 |
325 | public long GetPropertyLong(string name)
326 | {
327 | Guard.AgainstDisposed(disposed, nameof(Mpv));
328 | Guard.AgainstNullOrEmptyOrWhiteSpaceString(name, nameof(name));
329 |
330 | var error = Functions.GetPropertyLong(Handle, name, MpvFormat.Int64, out long value);
331 | if (error != MpvError.Success)
332 | throw MpvAPIException.FromError(error, Functions);
333 |
334 | return value;
335 | }
336 |
337 | public double GetPropertyDouble(string name)
338 | {
339 | Guard.AgainstDisposed(disposed, nameof(Mpv));
340 | Guard.AgainstNullOrEmptyOrWhiteSpaceString(name, nameof(name));
341 |
342 | var error = Functions.GetPropertyDouble(Handle, name, MpvFormat.Double, out double value);
343 | if (error != MpvError.Success)
344 | throw MpvAPIException.FromError(error, Functions);
345 |
346 | return value;
347 | }
348 |
349 | public string GetPropertyString(string name)
350 | {
351 | Guard.AgainstDisposed(disposed, nameof(Mpv));
352 | Guard.AgainstNullOrEmptyOrWhiteSpaceString(name, nameof(name));
353 |
354 | var stringPtr = Functions.GetPropertyString(Handle, name);
355 | if (stringPtr == IntPtr.Zero)
356 | throw new MpvAPIException("Failed to get property string, invalid pointer.");
357 |
358 | try
359 | {
360 | return MpvMarshal.GetManagedUTF8StringFromPtr(stringPtr);
361 | }
362 | finally
363 | {
364 | Functions.Free(stringPtr);
365 | }
366 | }
367 |
368 | public void ObserveProperty(string name, MpvFormat format, ulong replyUserData)
369 | {
370 | Guard.AgainstDisposed(disposed, nameof(Mpv));
371 | Guard.AgainstNullOrEmptyOrWhiteSpaceString(name, nameof(name));
372 |
373 | var error = Functions.ObserveProperty(Handle, replyUserData, name, format);
374 | if (error != MpvError.Success)
375 | throw MpvAPIException.FromError(error, functions);
376 | }
377 |
378 | public int UnobserveProperty(ulong registeredReplyUserData)
379 | {
380 | Guard.AgainstDisposed(disposed, nameof(Mpv));
381 |
382 | var result = Functions.UnobserveProperty(Handle, registeredReplyUserData);
383 | if (result < 1)
384 | {
385 | var error = (MpvError)result;
386 | throw MpvAPIException.FromError(error, functions);
387 | }
388 |
389 | return result;
390 | }
391 |
392 | public string EventName(MpvEventID eventID)
393 | {
394 | Guard.AgainstDisposed(disposed, nameof(Mpv));
395 |
396 | return Functions.EventName(eventID);
397 | }
398 |
399 | public void RequestEvent(MpvEventID eventID, bool enabled)
400 | {
401 | Guard.AgainstDisposed(disposed, nameof(Mpv));
402 |
403 | var error = Functions.RequestEvent(Handle, eventID, enabled);
404 | if (error != MpvError.Success)
405 | throw MpvAPIException.FromError(error, functions);
406 | }
407 |
408 | public void RequestLogMessages(MpvLogLevel logLevel)
409 | {
410 | Guard.AgainstDisposed(disposed, nameof(Mpv));
411 |
412 | var stringLogLevel = MpvLogLevelHelper.GetStringForLogLevel(logLevel);
413 |
414 | var error = Functions.RequestLogMessages(Handle, stringLogLevel);
415 | if (error != MpvError.Success)
416 | throw MpvAPIException.FromError(error, Functions);
417 | }
418 |
419 | public void Dispose()
420 | {
421 | Dispose(true);
422 | GC.SuppressFinalize(this);
423 | }
424 |
425 | protected virtual void Dispose(bool disposing)
426 | {
427 | if (!disposed)
428 | {
429 | // NOTE
430 | // The order of disposal is important here. Functions.TerminateDestroy is
431 | // responsible for disposing of unmanaged resources on mpv's side.
432 | // Inside the Dispose method of the MpvFunctions object, Windows.FreeLibrary
433 | // is used to free the resources of the loaded mpv DLL.
434 | // Windows.FreeLibrary MUST COME AFTER Functions.TerminarDestroy
435 |
436 | // The event loop calls into mpv so we can't TerminateDestrot yet!
437 | if (disposing && EventLoop is IDisposable disposableEventLoop)
438 | disposableEventLoop.Dispose();
439 |
440 | Functions.TerminateDestroy(Handle);
441 |
442 | if (disposing && Functions is IDisposable disposableFunctions)
443 | disposableFunctions.Dispose();
444 |
445 | disposed = true;
446 | }
447 | }
448 |
449 | ~Mpv()
450 | {
451 | Dispose(false);
452 | }
453 | }
454 | }
--------------------------------------------------------------------------------
/src/Mpv.NET/API/MpvEventLoop.cs:
--------------------------------------------------------------------------------
1 | using Mpv.NET.API.Interop;
2 | using System;
3 | using System.Threading;
4 | using System.Threading.Tasks;
5 |
6 | namespace Mpv.NET.API
7 | {
8 | public class MpvEventLoop : IMpvEventLoop, IDisposable
9 | {
10 | public bool IsRunning { get => isRunning; private set => isRunning = value; }
11 |
12 | public Action Callback { get; set; }
13 |
14 | public IntPtr MpvHandle
15 | {
16 | get => mpvHandle;
17 | private set
18 | {
19 | if (value == IntPtr.Zero)
20 | throw new ArgumentException("Mpv handle is invalid.");
21 |
22 | mpvHandle = value;
23 | }
24 | }
25 |
26 | public IMpvFunctions Functions
27 | {
28 | get => functions;
29 | set
30 | {
31 | Guard.AgainstNull(value);
32 |
33 | functions = value;
34 | }
35 | }
36 |
37 | private IntPtr mpvHandle;
38 | private IMpvFunctions functions;
39 |
40 | private Task eventLoopTask;
41 |
42 | private bool disposed = false;
43 | private volatile bool isRunning;
44 |
45 | public MpvEventLoop(Action callback, IntPtr mpvHandle, IMpvFunctions functions)
46 | {
47 | Callback = callback;
48 | MpvHandle = mpvHandle;
49 | Functions = functions;
50 | }
51 |
52 | public void Start()
53 | {
54 | Guard.AgainstDisposed(disposed, nameof(MpvEventLoop));
55 |
56 | DisposeEventLoopTask();
57 |
58 | IsRunning = true;
59 |
60 | eventLoopTask = new Task(EventLoopTaskHandler);
61 | eventLoopTask.Start();
62 | }
63 |
64 | public void Stop()
65 | {
66 | Guard.AgainstDisposed(disposed, nameof(MpvEventLoop));
67 |
68 | IsRunning = false;
69 |
70 | if (Task.CurrentId == eventLoopTask.Id)
71 | {
72 | return;
73 | }
74 |
75 | // Wake up WaitEvent in the event loop thread
76 | // so we can stop it.
77 | Functions.Wakeup(mpvHandle);
78 |
79 | eventLoopTask.Wait();
80 | }
81 |
82 | private void EventLoopTaskHandler()
83 | {
84 | while (IsRunning)
85 | {
86 | var eventPtr = Functions.WaitEvent(mpvHandle, Timeout.Infinite);
87 | if (eventPtr != IntPtr.Zero)
88 | {
89 | var @event = MpvMarshal.PtrToStructure(eventPtr);
90 | if (@event.ID != MpvEventID.None)
91 | Callback?.Invoke(@event);
92 | }
93 | }
94 | }
95 |
96 | private void DisposeEventLoopTask()
97 | {
98 | eventLoopTask?.Dispose();
99 | }
100 |
101 | public void Dispose()
102 | {
103 | Dispose(true);
104 | }
105 |
106 | protected virtual void Dispose(bool disposing)
107 | {
108 | if (disposing)
109 | {
110 | if (!disposed)
111 | {
112 | Stop();
113 |
114 | DisposeEventLoopTask();
115 | }
116 |
117 | disposed = true;
118 | }
119 | }
120 | }
121 | }
--------------------------------------------------------------------------------
/src/Mpv.NET/API/MpvEvents.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Mpv.NET.API
4 | {
5 | public partial class Mpv
6 | {
7 | public event EventHandler Shutdown;
8 | public event EventHandler LogMessage;
9 | public event EventHandler GetPropertyReply;
10 | public event EventHandler SetPropertyReply;
11 | public event EventHandler CommandReply;
12 | public event EventHandler StartFile;
13 | public event EventHandler EndFile;
14 | public event EventHandler FileLoaded;
15 |
16 | [Obsolete("Deprecated in favour of ObserveProperty on \"track-list\".")]
17 | public event EventHandler TracksChanged;
18 |
19 | [Obsolete("Deprecated in favour of ObserveProperty on \"vid\", \"aid\" and \"sid\".")]
20 | public event EventHandler TrackSwitched;
21 |
22 | public event EventHandler Idle;
23 |
24 | [Obsolete("Deprecated in favour of ObserveProperty on \"pause\".")]
25 | public event EventHandler Pause;
26 |
27 | [Obsolete("Deprecated in favour of ObserveProperty on \"pause\".")]
28 | public event EventHandler Unpause;
29 |
30 | public event EventHandler Tick;
31 |
32 | [Obsolete("This event does not occur anymore, included for compatibility.")]
33 | public event EventHandler ScriptInputDispatch;
34 |
35 | public event EventHandler ClientMessage;
36 | public event EventHandler VideoReconfig;
37 | public event EventHandler AudioReconfig;
38 |
39 | [Obsolete("Deprecated in favour of ObserveProperty on \"metadata\".")]
40 | public event EventHandler MetadataUpdate;
41 |
42 | public event EventHandler Seek;
43 | public event EventHandler PlaybackRestart;
44 | public event EventHandler PropertyChange;
45 |
46 | [Obsolete("Deprecated in favour of ObserveProperty on \"chapter\".")]
47 | public event EventHandler ChapterChange;
48 |
49 | public event EventHandler QueueOverflow;
50 |
51 | private void EventCallback(MpvEvent @event)
52 | {
53 | var eventId = @event.ID;
54 | switch (eventId)
55 | {
56 | // Events that can be "handled"
57 | case MpvEventID.Shutdown:
58 | HandleShutdown();
59 | break;
60 | case MpvEventID.LogMessage:
61 | HandleLogMessage(@event);
62 | break;
63 | case MpvEventID.GetPropertyReply:
64 | HandleGetPropertyReply(@event);
65 | break;
66 | case MpvEventID.SetPropertyReply:
67 | HandleSetPropertyReply(@event);
68 | break;
69 | case MpvEventID.CommandReply:
70 | HandleCommandReply(@event);
71 | break;
72 | case MpvEventID.EndFile:
73 | HandleEndFile(@event);
74 | break;
75 | case MpvEventID.ClientMessage:
76 | HandleClientMessage(@event);
77 | break;
78 | case MpvEventID.PropertyChange:
79 | HandlePropertyChange(@event);
80 | break;
81 |
82 | // Todo: Find a better/shorter way of doing this?
83 | // I tried to put the EventHandlers below into a dictionary
84 | // and invoke them based on the event ID but a reference
85 | // to the EventHandler didn't seem to update when a handler
86 | // was attached to that property and therefore we couldn't invoke
87 | // it.
88 |
89 | // All other simple notification events.
90 | case MpvEventID.StartFile:
91 | InvokeSimple(StartFile);
92 | break;
93 | case MpvEventID.FileLoaded:
94 | InvokeSimple(FileLoaded);
95 | break;
96 | case MpvEventID.TracksChanged:
97 | InvokeSimple(TracksChanged);
98 | break;
99 | case MpvEventID.TrackSwitched:
100 | InvokeSimple(TrackSwitched);
101 | break;
102 | case MpvEventID.Idle:
103 | InvokeSimple(Idle);
104 | break;
105 | case MpvEventID.Pause:
106 | InvokeSimple(Pause);
107 | break;
108 | case MpvEventID.Unpause:
109 | InvokeSimple(Unpause);
110 | break;
111 | case MpvEventID.Tick:
112 | InvokeSimple(Tick);
113 | break;
114 | case MpvEventID.ScriptInputDispatch:
115 | InvokeSimple(ScriptInputDispatch);
116 | break;
117 | case MpvEventID.VideoReconfig:
118 | InvokeSimple(VideoReconfig);
119 | break;
120 | case MpvEventID.AudioReconfig:
121 | InvokeSimple(AudioReconfig);
122 | break;
123 | case MpvEventID.MetadataUpdate:
124 | InvokeSimple(MetadataUpdate);
125 | break;
126 | case MpvEventID.Seek:
127 | InvokeSimple(Seek);
128 | break;
129 | case MpvEventID.PlaybackRestart:
130 | InvokeSimple(PlaybackRestart);
131 | break;
132 | case MpvEventID.ChapterChange:
133 | InvokeSimple(ChapterChange);
134 | break;
135 | case MpvEventID.QueueOverflow:
136 | InvokeSimple(QueueOverflow);
137 | break;
138 | }
139 | }
140 |
141 | private void HandleShutdown()
142 | {
143 | eventLoop.Stop();
144 | Shutdown?.Invoke(this, EventArgs.Empty);
145 | }
146 |
147 | private void HandleLogMessage(MpvEvent @event)
148 | {
149 | if (LogMessage == null)
150 | return;
151 |
152 | var logMessage = @event.MarshalDataToStruct();
153 | if (logMessage.HasValue)
154 | {
155 | var eventArgs = new MpvLogMessageEventArgs(logMessage.Value);
156 | LogMessage.Invoke(this, eventArgs);
157 | }
158 | }
159 |
160 | private void HandleGetPropertyReply(MpvEvent @event)
161 | {
162 | if (GetPropertyReply == null)
163 | return;
164 |
165 | var eventProperty = @event.MarshalDataToStruct();
166 | if (eventProperty.HasValue)
167 | {
168 | var replyUserData = @event.ReplyUserData;
169 | var error = @event.Error;
170 |
171 | var eventArgs = new MpvGetPropertyReplyEventArgs(replyUserData, error, eventProperty.Value);
172 | GetPropertyReply.Invoke(this, eventArgs);
173 | }
174 | }
175 |
176 | private void HandleSetPropertyReply(MpvEvent @event)
177 | {
178 | if (SetPropertyReply == null)
179 | return;
180 |
181 | var replyUserData = @event.ReplyUserData;
182 | var error = @event.Error;
183 |
184 | var eventArgs = new MpvSetPropertyReplyEventArgs(replyUserData, error);
185 | SetPropertyReply.Invoke(this, eventArgs);
186 | }
187 |
188 | private void HandleCommandReply(MpvEvent @event)
189 | {
190 | if (CommandReply == null)
191 | return;
192 |
193 | var replyUserData = @event.ReplyUserData;
194 | var error = @event.Error;
195 |
196 | var eventArgs = new MpvCommandReplyEventArgs(replyUserData, error);
197 | CommandReply.Invoke(this, eventArgs);
198 | }
199 |
200 | private void HandleEndFile(MpvEvent @event)
201 | {
202 | if (EndFile == null)
203 | return;
204 |
205 | var eventEndFile = @event.MarshalDataToStruct();
206 | if (eventEndFile.HasValue)
207 | {
208 | var eventArgs = new MpvEndFileEventArgs(eventEndFile.Value);
209 | EndFile.Invoke(this, eventArgs);
210 | }
211 | }
212 |
213 | private void HandleClientMessage(MpvEvent @event)
214 | {
215 | if (ClientMessage == null)
216 | return;
217 |
218 | var eventClientMessage = @event.MarshalDataToStruct();
219 | if (eventClientMessage.HasValue)
220 | {
221 | var eventArgs = new MpvClientMessageEventArgs(eventClientMessage.Value);
222 | ClientMessage.Invoke(this, eventArgs);
223 | }
224 | }
225 |
226 | private void HandlePropertyChange(MpvEvent @event)
227 | {
228 | if (PropertyChange == null)
229 | return;
230 |
231 | var eventProperty = @event.MarshalDataToStruct();
232 | if (eventProperty.HasValue)
233 | {
234 | var replyUserData = @event.ReplyUserData;
235 |
236 | var eventArgs = new MpvPropertyChangeEventArgs(replyUserData, eventProperty.Value);
237 | PropertyChange.Invoke(this, eventArgs);
238 | }
239 | }
240 |
241 | private void InvokeSimple(EventHandler eventHandler)
242 | {
243 | eventHandler?.Invoke(this, EventArgs.Empty);
244 | }
245 | }
246 | }
--------------------------------------------------------------------------------
/src/Mpv.NET/API/MpvFunctionDeclarations.cs:
--------------------------------------------------------------------------------
1 | using Mpv.NET.API.Interop;
2 | using System;
3 | using System.Runtime.InteropServices;
4 |
5 | namespace Mpv.NET.API
6 | {
7 | // Standard delegates
8 | // Taken from: https://github.com/mpv-player/mpv/blob/master/libmpv/client.h
9 |
10 | [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
11 | public delegate long MpvClientAPIVersion();
12 |
13 | [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
14 | [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(MpvStringMarshaler))]
15 | public delegate string MpvErrorString(MpvError error);
16 |
17 | [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
18 | public delegate void MpvFree(IntPtr data);
19 |
20 | [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
21 | [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(MpvStringMarshaler))]
22 | public delegate string MpvClientName(IntPtr mpvHandle);
23 |
24 | [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
25 | public delegate IntPtr MpvCreate();
26 |
27 | [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
28 | public delegate MpvError MpvInitialise(IntPtr mpvHandle);
29 |
30 | [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
31 | public delegate void MpvDetachDestroy(IntPtr mpvHandle);
32 |
33 | [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
34 | public delegate void MpvTerminateDestroy(IntPtr mpvHandle);
35 |
36 | [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
37 | public delegate IntPtr MpvCreateClient(
38 | IntPtr mpvHandle,
39 | [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(MpvStringMarshaler))]
40 | out string name);
41 |
42 | [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
43 | public delegate MpvError MpvLoadConfigFile(
44 | IntPtr mpvHandle,
45 | [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(MpvStringMarshaler), MarshalCookie = "free-com")]
46 | string fileName);
47 |
48 | [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
49 | public delegate long MpvGetTimeUs(IntPtr mpvHandle);
50 |
51 | [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
52 | public delegate MpvError MpvSetOption(
53 | IntPtr mpvHandle,
54 | [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(MpvStringMarshaler), MarshalCookie = "free-com")]
55 | string name,
56 | MpvFormat format,
57 | IntPtr data);
58 |
59 | [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
60 | public delegate MpvError MpvSetOptionString(
61 | IntPtr mpvHandle,
62 | [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(MpvStringMarshaler), MarshalCookie = "free-com")]
63 | string name,
64 | [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(MpvStringMarshaler), MarshalCookie = "free-com")]
65 | string data);
66 |
67 | [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
68 | public delegate MpvError MpvCommand(IntPtr mpvHandle, IntPtr args);
69 |
70 | [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
71 | public delegate MpvError MpvCommandAsync(IntPtr mpvHandle, ulong replyUserData, IntPtr args);
72 |
73 | [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
74 | public delegate MpvError MpvSetProperty(
75 | IntPtr mpvHandle,
76 | [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(MpvStringMarshaler), MarshalCookie = "free-com")]
77 | string name,
78 | MpvFormat format,
79 | IntPtr data);
80 |
81 | [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
82 | public delegate MpvError MpvSetPropertyString(
83 | IntPtr mpvHandle,
84 | [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(MpvStringMarshaler), MarshalCookie = "free-com")]
85 | string name,
86 | [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(MpvStringMarshaler), MarshalCookie = "free-com")]
87 | string data);
88 |
89 | [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
90 | public delegate MpvError MpvSetPropertyAsync(
91 | IntPtr mpvHandle,
92 | ulong replyUserData,
93 | [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(MpvStringMarshaler), MarshalCookie = "free-com")]
94 | string name,
95 | MpvFormat format,
96 | IntPtr data);
97 |
98 | [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
99 | public delegate MpvError MpvGetProperty(
100 | IntPtr mpvHandle,
101 | [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(MpvStringMarshaler), MarshalCookie = "free-com")]
102 | string name,
103 | MpvFormat format,
104 | out IntPtr data);
105 |
106 | // Todo: Figure out how to free the pointer for the return value using mpv_free inside a custom marshaler. Until then
107 | // we'll have to keep the IntPtr. Same goes for the OSD variant below.
108 | [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
109 | public delegate IntPtr MpvGetPropertyString(
110 | IntPtr mpvHandle,
111 | [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(MpvStringMarshaler), MarshalCookie = "free-com")]
112 | string name);
113 |
114 | [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
115 | public delegate IntPtr MpvGetPropertyOSDString(
116 | IntPtr mpvHandle,
117 | [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(MpvStringMarshaler), MarshalCookie = "free-com")]
118 | string name);
119 |
120 | [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
121 | public delegate MpvError MpvGetPropertyAsync(
122 | IntPtr mpvHandle,
123 | ulong replyUserData,
124 | [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(MpvStringMarshaler), MarshalCookie = "free-com")]
125 | string name,
126 | MpvFormat format);
127 |
128 | [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
129 | public delegate MpvError MpvObserveProperty(
130 | IntPtr mpvHandle,
131 | ulong replyUserData,
132 | [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(MpvStringMarshaler), MarshalCookie = "free-com")]
133 | string name,
134 | MpvFormat format);
135 |
136 | [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
137 | public delegate int MpvUnobserveProperty(IntPtr mpvHandle, ulong registeredReplyUserData);
138 |
139 | [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
140 | [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(MpvStringMarshaler))]
141 | public delegate string MpvEventName(MpvEventID eventID);
142 |
143 | [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
144 | public delegate MpvError MpvRequestEvent(
145 | IntPtr mpvHandle,
146 | MpvEventID eventID,
147 | [MarshalAs(UnmanagedType.I1)]
148 | bool enable);
149 |
150 | [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
151 | public delegate MpvError MpvRequestLogMessages(
152 | IntPtr mpvHandle,
153 | [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(MpvStringMarshaler), MarshalCookie = "free-com")]
154 | string minLevel);
155 |
156 | [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
157 | public delegate IntPtr MpvWaitEvent(IntPtr mpvHandle, double timeout);
158 |
159 | [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
160 | public delegate void MpvWakeup(IntPtr mpvHandle);
161 |
162 | [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
163 | public delegate void MpvSetWakeupCallback(
164 | IntPtr mpvHandle,
165 | [MarshalAs(UnmanagedType.FunctionPtr)]
166 | MpvWakeupCallback cb,
167 | IntPtr d);
168 |
169 | [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
170 | public delegate int MpvGetWakeupPipe(IntPtr mpvHandle);
171 |
172 | [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
173 | public delegate void MpvWaitAsyncRequests(IntPtr mpvHandle);
174 |
175 | // Not strictly part of the C API but are used to invoke mpv_get_property with other value data types.
176 |
177 | [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
178 | public delegate MpvError MpvGetPropertyDouble(
179 | IntPtr mpvHandle,
180 | [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(MpvStringMarshaler), MarshalCookie = "free-com")]
181 | string name,
182 | MpvFormat format,
183 | out double data);
184 |
185 | [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
186 | public delegate MpvError MpvGetPropertyLong(
187 | IntPtr mpvHandle,
188 | [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(MpvStringMarshaler), MarshalCookie = "free-com")]
189 | string name,
190 | MpvFormat format,
191 | out long data);
192 |
193 | // Other
194 |
195 | public delegate void MpvWakeupCallback(IntPtr d);
196 | }
--------------------------------------------------------------------------------
/src/Mpv.NET/API/MpvFunctions.cs:
--------------------------------------------------------------------------------
1 | using Mpv.NET.API.Interop;
2 | using System;
3 |
4 | namespace Mpv.NET.API
5 | {
6 | public class MpvFunctions : IMpvFunctions, IDisposable
7 | {
8 | public MpvClientAPIVersion ClientAPIVersion { get; private set; }
9 | public MpvErrorString ErrorString { get; private set; }
10 | public MpvFree Free { get; private set; }
11 | public MpvClientName ClientName { get; private set; }
12 | public MpvCreate Create { get; private set; }
13 | public MpvInitialise Initialise { get; private set; }
14 | public MpvDetachDestroy DetachDestroy { get; private set; }
15 | public MpvTerminateDestroy TerminateDestroy { get; private set; }
16 | public MpvCreateClient CreateClient { get; private set; }
17 | public MpvLoadConfigFile LoadConfigFile { get; private set; }
18 | public MpvGetTimeUs GetTimeUs { get; private set; }
19 | public MpvSetOption SetOption { get; private set; }
20 | public MpvSetOptionString SetOptionString { get; private set; }
21 | public MpvCommand Command { get; private set; }
22 | public MpvCommandAsync CommandAsync { get; private set; }
23 | public MpvSetProperty SetProperty { get; private set; }
24 | public MpvSetPropertyString SetPropertyString { get; private set; }
25 | public MpvSetPropertyAsync SetPropertyAsync { get; private set; }
26 | public MpvGetProperty GetProperty { get; private set; }
27 | public MpvGetPropertyString GetPropertyString { get; private set; }
28 | public MpvGetPropertyOSDString GetPropertyOSDString { get; private set; }
29 | public MpvGetPropertyAsync GetPropertyAsync { get; private set; }
30 | public MpvObserveProperty ObserveProperty { get; private set; }
31 | public MpvUnobserveProperty UnobserveProperty { get; private set; }
32 | public MpvEventName EventName { get; private set; }
33 | public MpvRequestEvent RequestEvent { get; private set; }
34 | public MpvRequestLogMessages RequestLogMessages { get; private set; }
35 | public MpvWaitEvent WaitEvent { get; private set; }
36 | public MpvWakeup Wakeup { get; private set; }
37 | public MpvSetWakeupCallback SetWakeupCallback { get; private set; }
38 | public MpvGetWakeupPipe GetWakeupPipe { get; private set; }
39 | public MpvWaitAsyncRequests WaitAsyncRequests { get; private set; }
40 |
41 | // Not strictly part of the C API but are used to invoke mpv_get_property with value data type.
42 | public MpvGetPropertyDouble GetPropertyDouble { get; private set; }
43 | public MpvGetPropertyLong GetPropertyLong { get; private set; }
44 |
45 | private IntPtr dllHandle;
46 |
47 | private bool disposed = false;
48 |
49 | public MpvFunctions(string dllPath)
50 | {
51 | LoadDll(dllPath);
52 |
53 | LoadFunctions();
54 | }
55 |
56 | private void LoadDll(string dllPath)
57 | {
58 | Guard.AgainstNullOrEmptyOrWhiteSpaceString(dllPath, nameof(dllPath));
59 |
60 | dllHandle = WinFunctions.LoadLibrary(dllPath);
61 | if (dllHandle == IntPtr.Zero)
62 | throw new MpvAPIException("Failed to load Mpv DLL. .NET apps by default are 32-bit so make sure you're loading the 32-bit DLL.");
63 | }
64 |
65 | private void LoadFunctions()
66 | {
67 | ClientAPIVersion = LoadFunction("mpv_client_api_version");
68 | ErrorString = LoadFunction("mpv_error_string");
69 | Free = LoadFunction("mpv_free");
70 | ClientName = LoadFunction("mpv_client_name");
71 | Create = LoadFunction("mpv_create");
72 | Initialise = LoadFunction("mpv_initialize");
73 | DetachDestroy = LoadFunction("mpv_detach_destroy");
74 | TerminateDestroy = LoadFunction("mpv_terminate_destroy");
75 | CreateClient = LoadFunction("mpv_create_client");
76 | LoadConfigFile = LoadFunction("mpv_load_config_file");
77 | GetTimeUs = LoadFunction("mpv_get_time_us");
78 | SetOption = LoadFunction("mpv_set_option");
79 | SetOptionString = LoadFunction("mpv_set_option_string");
80 | Command = LoadFunction("mpv_command");
81 | CommandAsync = LoadFunction("mpv_command_async");
82 | SetProperty = LoadFunction("mpv_set_property");
83 | SetPropertyString = LoadFunction("mpv_set_property_string");
84 | SetPropertyAsync = LoadFunction("mpv_set_property_async");
85 | GetProperty = LoadFunction("mpv_get_property");
86 | GetPropertyString = LoadFunction("mpv_get_property_string");
87 | GetPropertyOSDString = LoadFunction("mpv_get_property_osd_string");
88 | GetPropertyAsync = LoadFunction("mpv_get_property_async");
89 | ObserveProperty = LoadFunction("mpv_observe_property");
90 | UnobserveProperty = LoadFunction("mpv_unobserve_property");
91 | EventName = LoadFunction("mpv_event_name");
92 | RequestEvent = LoadFunction("mpv_request_event");
93 | RequestLogMessages = LoadFunction("mpv_request_log_messages");
94 | WaitEvent = LoadFunction("mpv_wait_event");
95 | Wakeup = LoadFunction("mpv_wakeup");
96 | SetWakeupCallback = LoadFunction("mpv_set_wakeup_callback");
97 | GetWakeupPipe = LoadFunction("mpv_get_wakeup_pipe");
98 | WaitAsyncRequests = LoadFunction("mpv_wait_async_requests");
99 |
100 | GetPropertyDouble = LoadFunction("mpv_get_property");
101 | GetPropertyLong = LoadFunction("mpv_get_property");
102 | }
103 |
104 | private TDelegate LoadFunction(string name) where TDelegate : class
105 | {
106 | var delegateValue = MpvMarshal.LoadUnmanagedFunction(dllHandle, name);
107 | if (delegateValue == null)
108 | throw new MpvAPIException($"Failed to load Mpv \"{name}\" function.");
109 |
110 | return delegateValue;
111 | }
112 |
113 | public void Dispose()
114 | {
115 | Dispose(true);
116 | }
117 |
118 | protected virtual void Dispose(bool disposing)
119 | {
120 | if (disposing)
121 | {
122 | if (!disposed)
123 | {
124 | WinFunctions.FreeLibrary(dllHandle);
125 | }
126 |
127 | disposed = true;
128 | }
129 | }
130 | }
131 | }
--------------------------------------------------------------------------------
/src/Mpv.NET/API/Structs/MpvEvent.cs:
--------------------------------------------------------------------------------
1 | using Mpv.NET.API.Interop;
2 | using System;
3 | using System.Runtime.InteropServices;
4 |
5 | namespace Mpv.NET.API
6 | {
7 | [StructLayout(LayoutKind.Sequential)]
8 | public struct MpvEvent
9 | {
10 | public MpvEventID ID;
11 |
12 | public MpvError Error;
13 |
14 | public ulong ReplyUserData;
15 |
16 | public IntPtr Data;
17 |
18 | public TData? MarshalDataToStruct() where TData : struct
19 | {
20 | if (Data == IntPtr.Zero)
21 | return default;
22 |
23 | return MpvMarshal.PtrToStructure(Data);
24 | }
25 | }
26 | }
--------------------------------------------------------------------------------
/src/Mpv.NET/API/Structs/MpvEventClientMessage.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Runtime.InteropServices;
3 |
4 | namespace Mpv.NET.API
5 | {
6 | [StructLayout(LayoutKind.Sequential)]
7 | public struct MpvEventClientMessage
8 | {
9 | public int NumArgs;
10 |
11 | public IntPtr Args;
12 | }
13 | }
--------------------------------------------------------------------------------
/src/Mpv.NET/API/Structs/MpvEventEndFile.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.InteropServices;
2 |
3 | namespace Mpv.NET.API
4 | {
5 | [StructLayout(LayoutKind.Sequential)]
6 | public struct MpvEventEndFile
7 | {
8 | public MpvEndFileReason Reason;
9 |
10 | public MpvError Error;
11 | }
12 | }
--------------------------------------------------------------------------------
/src/Mpv.NET/API/Structs/MpvEventProperty.cs:
--------------------------------------------------------------------------------
1 | using Mpv.NET.API.Interop;
2 | using System;
3 | using System.Runtime.InteropServices;
4 |
5 | namespace Mpv.NET.API
6 | {
7 | [StructLayout(LayoutKind.Sequential)]
8 | public struct MpvEventProperty
9 | {
10 | public string Name;
11 |
12 | public MpvFormat Format;
13 |
14 | public IntPtr Data;
15 |
16 | public string DataString
17 | {
18 | get
19 | {
20 | if (Format == MpvFormat.None || Data == IntPtr.Zero)
21 | return default;
22 |
23 | if (Format != MpvFormat.String)
24 | throw new MpvAPIException("Data is not a string.");
25 |
26 | var innerPtr = Marshal.ReadIntPtr(Data);
27 |
28 | return MpvMarshal.GetManagedUTF8StringFromPtr(innerPtr);
29 | }
30 | }
31 |
32 | public long DataLong
33 | {
34 | get
35 | {
36 | if (Format == MpvFormat.None || Data == IntPtr.Zero)
37 | return default;
38 |
39 | if (Format != MpvFormat.Int64)
40 | throw new MpvAPIException("Data is not a long.");
41 |
42 | return Marshal.ReadInt64(Data);
43 | }
44 | }
45 |
46 | public double DataDouble
47 | {
48 | get
49 | {
50 | if (Format == MpvFormat.None || Data == IntPtr.Zero)
51 | return default;
52 |
53 | if (Format != MpvFormat.Double)
54 | throw new MpvAPIException("Data is not a double.");
55 |
56 | var doubleBytes = new byte[sizeof(double)];
57 | Marshal.Copy(Data, doubleBytes, 0, sizeof(double));
58 |
59 | return BitConverter.ToDouble(doubleBytes, 0);
60 | }
61 | }
62 | }
63 | }
--------------------------------------------------------------------------------
/src/Mpv.NET/API/Structs/MpvLogMessage.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.InteropServices;
2 |
3 | namespace Mpv.NET.API
4 | {
5 | [StructLayout(LayoutKind.Sequential)]
6 | public struct MpvLogMessage
7 | {
8 | public string Prefix;
9 |
10 | public string Level;
11 |
12 | public string Text;
13 |
14 | public MpvLogLevel LogLevel;
15 | }
16 | }
--------------------------------------------------------------------------------
/src/Mpv.NET/Guard.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Mpv.NET
4 | {
5 | internal static class Guard
6 | {
7 | public static void AgainstDisposed(bool disposed, string objectName)
8 | {
9 | if (disposed)
10 | throw new ObjectDisposedException(objectName);
11 | }
12 |
13 | public static void AgainstNull(object value)
14 | {
15 | if (value == null)
16 | throw new ArgumentNullException();
17 | }
18 |
19 | public static void AgainstNull(object value, string name)
20 | {
21 | if (value == null)
22 | throw new ArgumentNullException(name);
23 | }
24 |
25 | public static void AgainstNullOrEmptyOrWhiteSpaceString(string value, string name)
26 | {
27 | AgainstNull(value, name);
28 |
29 | if (string.IsNullOrWhiteSpace(value))
30 | throw new ArgumentException(name);
31 | }
32 | }
33 | }
--------------------------------------------------------------------------------
/src/Mpv.NET/Mpv.NET.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/Mpv.NET/Player/EventArgs.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Mpv.NET.Player
4 | {
5 | public class MpvPlayerPositionChangedEventArgs : EventArgs
6 | {
7 | public TimeSpan NewPosition { get; private set; }
8 |
9 | public MpvPlayerPositionChangedEventArgs(double newPosition)
10 | {
11 | NewPosition = TimeSpan.FromSeconds(newPosition);
12 | }
13 | }
14 | }
--------------------------------------------------------------------------------
/src/Mpv.NET/Player/IMpvPlayer.cs:
--------------------------------------------------------------------------------
1 | using Mpv.NET.API;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Threading.Tasks;
5 |
6 | namespace Mpv.NET.Player
7 | {
8 | public interface IMpvPlayer
9 | {
10 | API.Mpv API { get; }
11 |
12 | string LibMpvPath { get; }
13 |
14 | string MediaTitle { get; }
15 |
16 | bool AutoPlay { get; set; }
17 |
18 | bool IsMediaLoaded { get; }
19 |
20 | bool IsPlaying { get; }
21 |
22 | bool IsPausedForCache { get; }
23 |
24 | double CacheDuration { get; }
25 |
26 | MpvLogLevel LogLevel { get; set; }
27 |
28 | YouTubeDlVideoQuality YouTubeDlVideoQuality { get; set; }
29 |
30 | int PlaylistEntryCount { get; }
31 |
32 | int PlaylistIndex { get; }
33 |
34 | KeepOpen KeepOpen { get; set; }
35 |
36 | bool Loop { get; set; }
37 |
38 | bool LoopPlaylist { get; set; }
39 |
40 | bool EndReached { get; }
41 |
42 | TimeSpan Duration { get; }
43 |
44 | TimeSpan Position { get; }
45 |
46 | TimeSpan Remaining { get; }
47 |
48 | int Volume { get; set; }
49 |
50 | double Speed { get; set; }
51 |
52 | event EventHandler MediaResumed;
53 |
54 | event EventHandler MediaPaused;
55 |
56 | event EventHandler MediaStartedBuffering;
57 |
58 | event EventHandler MediaEndedBuffering;
59 |
60 | event EventHandler MediaLoaded;
61 |
62 | event EventHandler MediaUnloaded;
63 |
64 | event EventHandler MediaFinished;
65 |
66 | event EventHandler MediaError;
67 |
68 | event EventHandler MediaStartedSeeking;
69 |
70 | event EventHandler MediaEndedSeeking;
71 |
72 | event EventHandler PositionChanged;
73 |
74 | void Load(string path, bool force = false);
75 |
76 | void LoadPlaylist(IEnumerable paths, bool force = false);
77 |
78 | Task SeekAsync(double position, bool relative = false);
79 |
80 | Task SeekAsync(TimeSpan position, bool relative = false);
81 |
82 | void Resume();
83 |
84 | void Pause();
85 |
86 | void Stop();
87 |
88 | Task RestartAsync();
89 |
90 | void AddAudio(string path);
91 |
92 | bool PlaylistNext();
93 |
94 | bool PlaylistPrevious();
95 |
96 | bool PlaylistRemove();
97 |
98 | bool PlaylistRemove(int index);
99 |
100 | bool PlaylistMove(int oldIndex, int newIndex);
101 |
102 | void PlaylistClear();
103 |
104 | void EnableYouTubeDl();
105 |
106 | void EnableYouTubeDl(string ytdlHookScriptPath);
107 |
108 | void NextFrame();
109 |
110 | void PreviousFrame();
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/src/Mpv.NET/Player/KeepOpen/KeepOpen.cs:
--------------------------------------------------------------------------------
1 | namespace Mpv.NET.Player
2 | {
3 | public enum KeepOpen
4 | {
5 | ///
6 | /// When the current media ends, play the next media or stop.
7 | ///
8 | No,
9 |
10 | ///
11 | /// Do not unload media when it reaches the end and it is the last entry in the playlist.
12 | ///
13 | Yes,
14 |
15 | ///
16 | /// Similar to "Yes" but it will not advance to the next entry in the playlist.
17 | ///
18 | Always
19 | }
20 | }
--------------------------------------------------------------------------------
/src/Mpv.NET/Player/KeepOpen/KeepOpenHelper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Mpv.NET.Player
4 | {
5 | internal static class KeepOpenHelper
6 | {
7 | public static string ToString(KeepOpen keepOpen)
8 | {
9 | switch (keepOpen)
10 | {
11 | case KeepOpen.Yes:
12 | return "yes";
13 | case KeepOpen.No:
14 | return "no";
15 | case KeepOpen.Always:
16 | return "always";
17 | }
18 |
19 | throw new ArgumentException("Invalid enumeration value.");
20 | }
21 |
22 | public static KeepOpen FromString(string keepOpenString)
23 | {
24 | switch (keepOpenString)
25 | {
26 | case "yes":
27 | return KeepOpen.Yes;
28 | case "no":
29 | return KeepOpen.No;
30 | case "always":
31 | return KeepOpen.Always;
32 | }
33 |
34 | throw new ArgumentException("Invalid value for \"keep-open\" property.");
35 | }
36 | }
37 | }
--------------------------------------------------------------------------------
/src/Mpv.NET/Player/LoadMethod/LoadMethod.cs:
--------------------------------------------------------------------------------
1 | namespace Mpv.NET.Player
2 | {
3 | public enum LoadMethod
4 | {
5 | ///
6 | /// Stop playback of current media and start new one.
7 | ///
8 | Replace,
9 |
10 | ///
11 | /// Append media to playlist.
12 | ///
13 | Append,
14 |
15 | ///
16 | /// Append media to playlist and play if nothing else is playing.
17 | ///
18 | AppendPlay
19 | }
20 | }
--------------------------------------------------------------------------------
/src/Mpv.NET/Player/LoadMethod/LoadMethodHelper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Mpv.NET.Player
4 | {
5 | internal static class LoadMethodHelper
6 | {
7 | public static string ToString(LoadMethod loadMethod)
8 | {
9 | switch (loadMethod)
10 | {
11 | case LoadMethod.Replace:
12 | return "replace";
13 | case LoadMethod.Append:
14 | return "append";
15 | case LoadMethod.AppendPlay:
16 | return "append-play";
17 | }
18 |
19 | throw new ArgumentException("Invalid enumeration value.");
20 | }
21 | }
22 | }
--------------------------------------------------------------------------------
/src/Mpv.NET/Player/MpvPlayerException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Mpv.NET.Player
4 | {
5 | public class MpvPlayerException : Exception
6 | {
7 | public MpvPlayerException()
8 | {
9 | }
10 |
11 | public MpvPlayerException(string message) : base(message)
12 | {
13 | }
14 |
15 | public MpvPlayerException(string message, Exception innerException) : base(message, innerException)
16 | {
17 | }
18 | }
19 | }
--------------------------------------------------------------------------------
/src/Mpv.NET/Player/MpvPlayerHelper.cs:
--------------------------------------------------------------------------------
1 | namespace Mpv.NET.Player
2 | {
3 | internal static class MpvPlayerHelper
4 | {
5 | public static string BoolToYesNo(bool yesNoBool) => yesNoBool ? "yes" : "no";
6 |
7 | public static bool YesNoToBool(string yesNoString) => yesNoString == "yes";
8 | }
9 | }
--------------------------------------------------------------------------------
/src/Mpv.NET/Player/WinFunctions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Runtime.InteropServices;
3 |
4 | namespace Mpv.NET.Player
5 | {
6 | internal static class WinFunctions
7 | {
8 | // https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-createwindowexa
9 | [DllImport("user32.dll", EntryPoint = "CreateWindowEx", CharSet = CharSet.Unicode)]
10 | internal static extern IntPtr CreateWindowEx(int dwExStyle,
11 | string lpClassName,
12 | string lpWindowName,
13 | int dwStyle,
14 | int x,
15 | int y,
16 | int nWidth,
17 | int nHeight,
18 | IntPtr hWndParent,
19 | IntPtr hMenu,
20 | IntPtr hInstance,
21 | [MarshalAs(UnmanagedType.AsAny)]
22 | object lpParam);
23 |
24 | // https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-destroywindow
25 | [DllImport("user32.dll", EntryPoint = "DestroyWindow", CharSet = CharSet.Unicode)]
26 | internal static extern bool DestroyWindow(IntPtr hwnd);
27 | }
28 | }
--------------------------------------------------------------------------------
/src/Mpv.NET/Player/YouTubeDlQuality/YouTubeDlQuality.cs:
--------------------------------------------------------------------------------
1 | namespace Mpv.NET.Player
2 | {
3 | public enum YouTubeDlVideoQuality
4 | {
5 | ///
6 | /// 144p
7 | ///
8 | Lowest = 144,
9 |
10 | ///
11 | /// 240p
12 | ///
13 | Low = 240,
14 |
15 | ///
16 | /// 360p
17 | ///
18 | LowMedium = 360,
19 |
20 | ///
21 | /// 480p
22 | ///
23 | Medium = 480,
24 |
25 | ///
26 | /// 720p
27 | ///
28 | MediumHigh = 720,
29 |
30 | ///
31 | /// 1080p
32 | ///
33 | High = 1080,
34 |
35 | ///
36 | /// Highest quality available.
37 | ///
38 | Highest
39 | }
40 | }
--------------------------------------------------------------------------------
/src/Mpv.NET/Player/YouTubeDlQuality/YouTubeDlQualityHelper.cs:
--------------------------------------------------------------------------------
1 | using System.Text;
2 |
3 | namespace Mpv.NET.Player
4 | {
5 | internal static class YouTubeDlHelperQuality
6 | {
7 | public static string GetFormatStringForVideoQuality(YouTubeDlVideoQuality videoQuality)
8 | {
9 | var stringBuilder = new StringBuilder("bestvideo");
10 |
11 | if (videoQuality != YouTubeDlVideoQuality.Highest)
12 | {
13 | stringBuilder.Append("[height<=");
14 | stringBuilder.Append((int)videoQuality);
15 | stringBuilder.Append("]");
16 | }
17 |
18 | stringBuilder.Append("+bestaudio/best");
19 |
20 | return stringBuilder.ToString();
21 | }
22 | }
23 | }
--------------------------------------------------------------------------------