├── .circleci
└── config.yml
├── .gitignore
├── .whitesource
├── LICENSE
├── Makefile
├── README.md
├── THIRD_PARTY_NOTICES
├── examples
├── live.go
├── live.mpd
├── ondemand.go
├── ondemand.mpd
└── ondemand_with_thumbnails.mpd
├── go.mod
├── go.sum
├── helpers
├── ptrs
│ └── ptrs.go
├── require
│ └── require.go
└── testfixtures
│ └── testfixtures.go
└── mpd
├── duration.go
├── duration_test.go
├── events.go
├── events_test.go
├── fixtures
├── adaptationset_switching.mpd
├── audio_channel_configuration.mpd
├── events.mpd
├── hbbtv_profile.mpd
├── inband_event_stream.mpd
├── invalid.mpd
├── live_profile.mpd
├── live_profile_dynamic.mpd
├── live_profile_multi_base_url.mpd
├── location.mpd
├── multiple_supplementals.mpd
├── newperiod.mpd
├── ondemand_profile.mpd
├── scte35.mpd
├── segment_list.mpd
├── segment_timeline.mpd
├── segment_timeline_multi_period.mpd
├── truncate.mpd
└── truncate_short.mpd
├── mpd.go
├── mpd_attr.go
├── mpd_read_write.go
├── mpd_read_write_test.go
├── mpd_test.go
├── pssh.go
├── pssh_test.go
├── scte35.go
├── scte35_test.go
├── segment.go
├── segment_list_test.go
├── segment_timeline_test.go
└── validate.go
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | ---
2 | version: 2
3 | jobs:
4 | build:
5 | docker:
6 | - image: cimg/go:1.23.4
7 | steps:
8 | - checkout
9 | - run: make test
10 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | bin/
2 | coverage/
3 | vendor/
4 | .idea/
5 | \#*
6 | *~
7 |
--------------------------------------------------------------------------------
/.whitesource:
--------------------------------------------------------------------------------
1 | {
2 | "settingsInheritedFrom": "zencoder/whitesource-config@main"
3 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright 2024 Brightcove, Inc.
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | ifdef VERBOSE
2 | V = -v
3 | X = -x
4 | else
5 | .SILENT:
6 | endif
7 |
8 | .DEFAULT_GOAL := all
9 |
10 | .PHONY: all
11 | all: test
12 |
13 | .PHONY: test
14 | test:
15 | go test $(V) ./... -race
16 |
17 | .PHONY: generate
18 | generate: export GENERATE_FIXTURES=true
19 | generate: test
20 |
21 | .PHONY: fmt
22 | fmt:
23 | go fmt $(X) ./...
24 |
25 | .PHONY: clean
26 | clean:
27 | go clean -i $(X) -cache -testcache
28 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # go-dash [](http://godoc.org/github.com/zencoder/go-dash/mpd)
2 |
3 | A [Go](https://golang.org) library for generating [MPEG-DASH](https://en.wikipedia.org/wiki/Dynamic_Adaptive_Streaming_over_HTTP) manifests.
4 |
5 | ## Install
6 |
7 | ```
8 | go get -u github.com/zencoder/go-dash
9 | ```
10 |
11 | ## Supported Features
12 |
13 | * Profiles
14 | * Live
15 | * On Demand
16 | * Adaption Sets / Representations / Roles
17 | * Audio
18 | * Video
19 | * Subtitles
20 | * Multiple periods (multi-part playlist)
21 | * DRM (ContentProtection)
22 | * PlayReady
23 | * Widevine
24 |
25 | ## Known Limitations (for now) (PRs welcome)
26 |
27 | * No PSSH/PRO generation
28 | * Limited Profile Support
29 |
30 | ## Example Usage
31 |
32 | See the [examples/](https://github.com/zencoder/go-dash/tree/master/examples) directory.
33 |
34 | ## Development
35 |
36 | ```
37 | make test
38 | ```
39 |
40 | ### CI
41 |
42 | [This project builds in Circle CI](https://circleci.com/gh/zencoder/go-dash/)
43 |
44 | ## License
45 |
46 | [Apache License Version 2.0](LICENSE)
47 |
--------------------------------------------------------------------------------
/THIRD_PARTY_NOTICES:
--------------------------------------------------------------------------------
1 | ### Third-Party Libraries
2 |
3 | #### Library: scte35-go
4 | - License: Apache License 2.0
5 | - URL: https://github.com/Comcast/scte35-go/tree/main
6 | - Copyright: None explicitly stated.
7 | - Notes: This library does not include a copyright notice, but it is licensed under the Apache License 2.0.
8 |
9 | This library is used in compliance with the Apache License, Version 2.0.
10 |
--------------------------------------------------------------------------------
/examples/live.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/zencoder/go-dash/v3/mpd"
7 | )
8 |
9 | func main() {
10 | m := mpd.NewMPD(mpd.DASH_PROFILE_LIVE, "PT6M16S", "PT1.97S")
11 |
12 | audioAS, _ := m.AddNewAdaptationSetAudio(mpd.DASH_MIME_TYPE_AUDIO_MP4, true, 1, "und")
13 | _, _ = audioAS.AddNewContentProtectionRoot("08e367028f33436ca5dd60ffe5571e60")
14 | _, _ = audioAS.AddNewContentProtectionSchemeWidevineWithPSSH([]byte{})
15 | _, _ = audioAS.AddNewContentProtectionSchemePlayready("mgIAAAEAAQCQAjwAVwBSAE0ASABFAEEARABFAFIAIAB4AG0AbABuAHMAPQAiAGgAdAB0AHAAOgAvAC8AcwBjAGgAZQBtAGEAcwAuAG0AaQBjAHIAbwBzAG8AZgB0AC4AYwBvAG0ALwBEAFIATQAvADIAMAAwADcALwAwADMALwBQAGwAYQB5AFIAZQBhAGQAeQBIAGUAYQBkAGUAcgAiACAAdgBlAHIAcwBpAG8AbgA9ACIANAAuADAALgAwAC4AMAAiAD4APABEAEEAVABBAD4APABQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsARQBZAEwARQBOAD4AMQA2ADwALwBLAEUAWQBMAEUATgA+ADwAQQBMAEcASQBEAD4AQQBFAFMAQwBUAFIAPAAvAEEATABHAEkARAA+ADwALwBQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsASQBEAD4AQQBtAGYAagBDAFQATwBQAGIARQBPAGwAMwBXAEQALwA1AG0AYwBlAGMAQQA9AD0APAAvAEsASQBEAD4APABDAEgARQBDAEsAUwBVAE0APgBCAEcAdwAxAGEAWQBaADEAWQBYAE0APQA8AC8AQwBIAEUAQwBLAFMAVQBNAD4APABMAEEAXwBVAFIATAA+AGgAdAB0AHAAOgAvAC8AcABsAGEAeQByAGUAYQBkAHkALgBkAGkAcgBlAGMAdAB0AGEAcABzAC4AbgBlAHQALwBwAHIALwBzAHYAYwAvAHIAaQBnAGgAdABzAG0AYQBuAGEAZwBlAHIALgBhAHMAbQB4ADwALwBMAEEAXwBVAFIATAA+ADwALwBEAEEAVABBAD4APAAvAFcAUgBNAEgARQBBAEQARQBSAD4A")
16 | _, _ = audioAS.SetNewSegmentTemplate(1968, "$RepresentationID$/audio/en/init.mp4", "$RepresentationID$/audio/en/seg-$Number$.m4f", 0, 1000)
17 | _, _ = audioAS.AddNewRepresentationAudio(44100, 67095, "mp4a.40.2", "800")
18 |
19 | videoAS, _ := m.AddNewAdaptationSetVideo(mpd.DASH_MIME_TYPE_VIDEO_MP4, "progressive", true, 1)
20 | _, _ = videoAS.AddNewContentProtectionRoot("08e367028f33436ca5dd60ffe5571e60")
21 | _, _ = videoAS.AddNewContentProtectionSchemeWidevineWithPSSH([]byte{})
22 | _, _ = videoAS.AddNewContentProtectionSchemePlayready("mgIAAAEAAQCQAjwAVwBSAE0ASABFAEEARABFAFIAIAB4AG0AbABuAHMAPQAiAGgAdAB0AHAAOgAvAC8AcwBjAGgAZQBtAGEAcwAuAG0AaQBjAHIAbwBzAG8AZgB0AC4AYwBvAG0ALwBEAFIATQAvADIAMAAwADcALwAwADMALwBQAGwAYQB5AFIAZQBhAGQAeQBIAGUAYQBkAGUAcgAiACAAdgBlAHIAcwBpAG8AbgA9ACIANAAuADAALgAwAC4AMAAiAD4APABEAEEAVABBAD4APABQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsARQBZAEwARQBOAD4AMQA2ADwALwBLAEUAWQBMAEUATgA+ADwAQQBMAEcASQBEAD4AQQBFAFMAQwBUAFIAPAAvAEEATABHAEkARAA+ADwALwBQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsASQBEAD4AQQBtAGYAagBDAFQATwBQAGIARQBPAGwAMwBXAEQALwA1AG0AYwBlAGMAQQA9AD0APAAvAEsASQBEAD4APABDAEgARQBDAEsAUwBVAE0APgBCAEcAdwAxAGEAWQBaADEAWQBYAE0APQA8AC8AQwBIAEUAQwBLAFMAVQBNAD4APABMAEEAXwBVAFIATAA+AGgAdAB0AHAAOgAvAC8AcABsAGEAeQByAGUAYQBkAHkALgBkAGkAcgBlAGMAdAB0AGEAcABzAC4AbgBlAHQALwBwAHIALwBzAHYAYwAvAHIAaQBnAGgAdABzAG0AYQBuAGEAZwBlAHIALgBhAHMAbQB4ADwALwBMAEEAXwBVAFIATAA+ADwALwBEAEEAVABBAD4APAAvAFcAUgBNAEgARQBBAEQARQBSAD4A")
23 | _, _ = videoAS.SetNewSegmentTemplate(1968, "$RepresentationID$/video/1/init.mp4", "$RepresentationID$/video/1/seg-$Number$.m4f", 0, 1000)
24 | _, _ = videoAS.AddNewRepresentationVideo(1518664, "avc1.4d401f", "800", "30000/1001", 960, 540)
25 | _, _ = videoAS.AddNewRepresentationVideo(1911775, "avc1.4d401f", "1000", "30000/1001", 1024, 576)
26 | _, _ = videoAS.AddNewRepresentationVideo(2295158, "avc1.4d401f", "1200", "30000/1001", 1024, 576)
27 | _, _ = videoAS.AddNewRepresentationVideo(2780732, "avc1.4d401f", "1500", "30000/1001", 1280, 720)
28 |
29 | subtitleAS, _ := m.AddNewAdaptationSetSubtitle(mpd.DASH_MIME_TYPE_SUBTITLE_VTT, "en", "Subtitle (En)")
30 | subtitleRep, _ := subtitleAS.AddNewRepresentationSubtitle(256, "subtitle_en")
31 | _ = subtitleRep.SetNewBaseURL("http://example.com/content/sintel/subtitles/subtitles_en.vtt")
32 | schemeIDURI := "urn:mpeg:dash:utc:direct:2014"
33 | value := "2019-10-23T15:56:29Z"
34 | m.UTCTiming = &mpd.DescriptorType{
35 | SchemeIDURI: &schemeIDURI,
36 | Value: &value,
37 | }
38 |
39 | mpdStr, _ := m.WriteToString()
40 | fmt.Println(mpdStr)
41 | }
42 |
--------------------------------------------------------------------------------
/examples/live.mpd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | mgIAAAEAAQCQAjwAVwBSAE0ASABFAEEARABFAFIAIAB4AG0AbABuAHMAPQAiAGgAdAB0AHAAOgAvAC8AcwBjAGgAZQBtAGEAcwAuAG0AaQBjAHIAbwBzAG8AZgB0AC4AYwBvAG0ALwBEAFIATQAvADIAMAAwADcALwAwADMALwBQAGwAYQB5AFIAZQBhAGQAeQBIAGUAYQBkAGUAcgAiACAAdgBlAHIAcwBpAG8AbgA9ACIANAAuADAALgAwAC4AMAAiAD4APABEAEEAVABBAD4APABQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsARQBZAEwARQBOAD4AMQA2ADwALwBLAEUAWQBMAEUATgA+ADwAQQBMAEcASQBEAD4AQQBFAFMAQwBUAFIAPAAvAEEATABHAEkARAA+ADwALwBQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsASQBEAD4AQQBtAGYAagBDAFQATwBQAGIARQBPAGwAMwBXAEQALwA1AG0AYwBlAGMAQQA9AD0APAAvAEsASQBEAD4APABDAEgARQBDAEsAUwBVAE0APgBCAEcAdwAxAGEAWQBaADEAWQBYAE0APQA8AC8AQwBIAEUAQwBLAFMAVQBNAD4APABMAEEAXwBVAFIATAA+AGgAdAB0AHAAOgAvAC8AcABsAGEAeQByAGUAYQBkAHkALgBkAGkAcgBlAGMAdAB0AGEAcABzAC4AbgBlAHQALwBwAHIALwBzAHYAYwAvAHIAaQBnAGgAdABzAG0AYQBuAGEAZwBlAHIALgBhAHMAbQB4ADwALwBMAEEAXwBVAFIATAA+ADwALwBEAEEAVABBAD4APAAvAFcAUgBNAEgARQBBAEQARQBSAD4A
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | mgIAAAEAAQCQAjwAVwBSAE0ASABFAEEARABFAFIAIAB4AG0AbABuAHMAPQAiAGgAdAB0AHAAOgAvAC8AcwBjAGgAZQBtAGEAcwAuAG0AaQBjAHIAbwBzAG8AZgB0AC4AYwBvAG0ALwBEAFIATQAvADIAMAAwADcALwAwADMALwBQAGwAYQB5AFIAZQBhAGQAeQBIAGUAYQBkAGUAcgAiACAAdgBlAHIAcwBpAG8AbgA9ACIANAAuADAALgAwAC4AMAAiAD4APABEAEEAVABBAD4APABQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsARQBZAEwARQBOAD4AMQA2ADwALwBLAEUAWQBMAEUATgA+ADwAQQBMAEcASQBEAD4AQQBFAFMAQwBUAFIAPAAvAEEATABHAEkARAA+ADwALwBQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsASQBEAD4AQQBtAGYAagBDAFQATwBQAGIARQBPAGwAMwBXAEQALwA1AG0AYwBlAGMAQQA9AD0APAAvAEsASQBEAD4APABDAEgARQBDAEsAUwBVAE0APgBCAEcAdwAxAGEAWQBaADEAWQBYAE0APQA8AC8AQwBIAEUAQwBLAFMAVQBNAD4APABMAEEAXwBVAFIATAA+AGgAdAB0AHAAOgAvAC8AcABsAGEAeQByAGUAYQBkAHkALgBkAGkAcgBlAGMAdAB0AGEAcABzAC4AbgBlAHQALwBwAHIALwBzAHYAYwAvAHIAaQBnAGgAdABzAG0AYQBuAGEAZwBlAHIALgBhAHMAbQB4ADwALwBMAEEAXwBVAFIATAA+ADwALwBEAEEAVABBAD4APAAvAFcAUgBNAEgARQBBAEQARQBSAD4A
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | http://example.com/content/sintel/subtitles/subtitles_en.vtt
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/examples/ondemand.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/zencoder/go-dash/v3/mpd"
7 | )
8 |
9 | func exampleOndemand() {
10 | m := mpd.NewMPD(mpd.DASH_PROFILE_ONDEMAND, "PT30S", "PT1.97S")
11 |
12 | audioAS, _ := m.AddNewAdaptationSetAudio(mpd.DASH_MIME_TYPE_AUDIO_MP4, true, 1, "und")
13 | _, _ = audioAS.AddNewContentProtectionRoot("08e367028f33436ca5dd60ffe5571e60")
14 | _, _ = audioAS.AddNewContentProtectionSchemeWidevineWithPSSH([]byte{})
15 | _, _ = audioAS.AddNewContentProtectionSchemePlayready("mgIAAAEAAQCQAjwAVwBSAE0ASABFAEEARABFAFIAIAB4AG0AbABuAHMAPQAiAGgAdAB0AHAAOgAvAC8AcwBjAGgAZQBtAGEAcwAuAG0AaQBjAHIAbwBzAG8AZgB0AC4AYwBvAG0ALwBEAFIATQAvADIAMAAwADcALwAwADMALwBQAGwAYQB5AFIAZQBhAGQAeQBIAGUAYQBkAGUAcgAiACAAdgBlAHIAcwBpAG8AbgA9ACIANAAuADAALgAwAC4AMAAiAD4APABEAEEAVABBAD4APABQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsARQBZAEwARQBOAD4AMQA2ADwALwBLAEUAWQBMAEUATgA+ADwAQQBMAEcASQBEAD4AQQBFAFMAQwBUAFIAPAAvAEEATABHAEkARAA+ADwALwBQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsASQBEAD4AQQBtAGYAagBDAFQATwBQAGIARQBPAGwAMwBXAEQALwA1AG0AYwBlAGMAQQA9AD0APAAvAEsASQBEAD4APABDAEgARQBDAEsAUwBVAE0APgBCAEcAdwAxAGEAWQBaADEAWQBYAE0APQA8AC8AQwBIAEUAQwBLAFMAVQBNAD4APABMAEEAXwBVAFIATAA+AGgAdAB0AHAAOgAvAC8AcABsAGEAeQByAGUAYQBkAHkALgBkAGkAcgBlAGMAdAB0AGEAcABzAC4AbgBlAHQALwBwAHIALwBzAHYAYwAvAHIAaQBnAGgAdABzAG0AYQBuAGEAZwBlAHIALgBhAHMAbQB4ADwALwBMAEEAXwBVAFIATAA+ADwALwBEAEEAVABBAD4APAAvAFcAUgBNAEgARQBBAEQARQBSAD4A")
16 |
17 | audioRep, _ := audioAS.AddNewRepresentationAudio(44100, 128558, "mp4a.40.5", "800k/audio-und")
18 | _ = audioRep.SetNewBaseURL("800k/output-audio-und.mp4")
19 | _, _ = audioRep.AddNewSegmentBase("629-756", "0-628")
20 |
21 | videoAS, _ := m.AddNewAdaptationSetVideo(mpd.DASH_MIME_TYPE_VIDEO_MP4, "progressive", true, 1)
22 | _, _ = videoAS.AddNewContentProtectionRoot("08e367028f33436ca5dd60ffe5571e60")
23 | _, _ = videoAS.AddNewContentProtectionSchemeWidevineWithPSSH([]byte{})
24 | _, _ = videoAS.AddNewContentProtectionSchemePlayready("mgIAAAEAAQCQAjwAVwBSAE0ASABFAEEARABFAFIAIAB4AG0AbABuAHMAPQAiAGgAdAB0AHAAOgAvAC8AcwBjAGgAZQBtAGEAcwAuAG0AaQBjAHIAbwBzAG8AZgB0AC4AYwBvAG0ALwBEAFIATQAvADIAMAAwADcALwAwADMALwBQAGwAYQB5AFIAZQBhAGQAeQBIAGUAYQBkAGUAcgAiACAAdgBlAHIAcwBpAG8AbgA9ACIANAAuADAALgAwAC4AMAAiAD4APABEAEEAVABBAD4APABQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsARQBZAEwARQBOAD4AMQA2ADwALwBLAEUAWQBMAEUATgA+ADwAQQBMAEcASQBEAD4AQQBFAFMAQwBUAFIAPAAvAEEATABHAEkARAA+ADwALwBQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsASQBEAD4AQQBtAGYAagBDAFQATwBQAGIARQBPAGwAMwBXAEQALwA1AG0AYwBlAGMAQQA9AD0APAAvAEsASQBEAD4APABDAEgARQBDAEsAUwBVAE0APgBCAEcAdwAxAGEAWQBaADEAWQBYAE0APQA8AC8AQwBIAEUAQwBLAFMAVQBNAD4APABMAEEAXwBVAFIATAA+AGgAdAB0AHAAOgAvAC8AcABsAGEAeQByAGUAYQBkAHkALgBkAGkAcgBlAGMAdAB0AGEAcABzAC4AbgBlAHQALwBwAHIALwBzAHYAYwAvAHIAaQBnAGgAdABzAG0AYQBuAGEAZwBlAHIALgBhAHMAbQB4ADwALwBMAEEAXwBVAFIATAA+ADwALwBEAEEAVABBAD4APAAvAFcAUgBNAEgARQBBAEQARQBSAD4A")
25 |
26 | videoRep1, _ := videoAS.AddNewRepresentationVideo(1100690, "avc1.4d401e", "800k/video-1", "30000/1001", 640, 360)
27 | _ = videoRep1.SetNewBaseURL("800k/output-video-1.mp4")
28 | _, _ = videoRep1.AddNewSegmentBase("686-813", "0-685")
29 |
30 | videoRep2, _ := videoAS.AddNewRepresentationVideo(1633516, "avc1.4d401f", "1200k/video-1", "30000/1001", 960, 540)
31 | _ = videoRep2.SetNewBaseURL("1200k/output-video-1.mp4")
32 | _, _ = videoRep2.AddNewSegmentBase("686-813", "0-685")
33 |
34 | subtitleAS, _ := m.AddNewAdaptationSetSubtitle(mpd.DASH_MIME_TYPE_SUBTITLE_VTT, "en", "Subtitle (En)")
35 | subtitleRep, _ := subtitleAS.AddNewRepresentationSubtitle(256, "captions_en")
36 | _ = subtitleRep.SetNewBaseURL("http://example.com/content/sintel/subtitles/subtitles_en.vtt")
37 |
38 | thumbnailsAS, _ := m.AddNewAdaptationSetThumbnails(mpd.DASH_MIME_TYPE_IMAGE_JPEG)
39 | _, _ = thumbnailsAS.SetNewSegmentTemplateThumbnails(1801800, "$RepresentationID$/$Number$.jpg", 0, 30000)
40 | _, _ = thumbnailsAS.AddNewRepresentationThumbnails("thumbnails", "5x4", "http://dashif.org/guidelines/thumbnail_tile", 50000, 1600, 720)
41 |
42 | mpdStr, _ := m.WriteToString()
43 | fmt.Println(mpdStr)
44 | }
45 |
--------------------------------------------------------------------------------
/examples/ondemand.mpd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | mgIAAAEAAQCQAjwAVwBSAE0ASABFAEEARABFAFIAIAB4AG0AbABuAHMAPQAiAGgAdAB0AHAAOgAvAC8AcwBjAGgAZQBtAGEAcwAuAG0AaQBjAHIAbwBzAG8AZgB0AC4AYwBvAG0ALwBEAFIATQAvADIAMAAwADcALwAwADMALwBQAGwAYQB5AFIAZQBhAGQAeQBIAGUAYQBkAGUAcgAiACAAdgBlAHIAcwBpAG8AbgA9ACIANAAuADAALgAwAC4AMAAiAD4APABEAEEAVABBAD4APABQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsARQBZAEwARQBOAD4AMQA2ADwALwBLAEUAWQBMAEUATgA+ADwAQQBMAEcASQBEAD4AQQBFAFMAQwBUAFIAPAAvAEEATABHAEkARAA+ADwALwBQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsASQBEAD4AQQBtAGYAagBDAFQATwBQAGIARQBPAGwAMwBXAEQALwA1AG0AYwBlAGMAQQA9AD0APAAvAEsASQBEAD4APABDAEgARQBDAEsAUwBVAE0APgBCAEcAdwAxAGEAWQBaADEAWQBYAE0APQA8AC8AQwBIAEUAQwBLAFMAVQBNAD4APABMAEEAXwBVAFIATAA+AGgAdAB0AHAAOgAvAC8AcABsAGEAeQByAGUAYQBkAHkALgBkAGkAcgBlAGMAdAB0AGEAcABzAC4AbgBlAHQALwBwAHIALwBzAHYAYwAvAHIAaQBnAGgAdABzAG0AYQBuAGEAZwBlAHIALgBhAHMAbQB4ADwALwBMAEEAXwBVAFIATAA+ADwALwBEAEEAVABBAD4APAAvAFcAUgBNAEgARQBBAEQARQBSAD4A
9 |
10 |
11 | 800k/output-audio-und.mp4
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | mgIAAAEAAQCQAjwAVwBSAE0ASABFAEEARABFAFIAIAB4AG0AbABuAHMAPQAiAGgAdAB0AHAAOgAvAC8AcwBjAGgAZQBtAGEAcwAuAG0AaQBjAHIAbwBzAG8AZgB0AC4AYwBvAG0ALwBEAFIATQAvADIAMAAwADcALwAwADMALwBQAGwAYQB5AFIAZQBhAGQAeQBIAGUAYQBkAGUAcgAiACAAdgBlAHIAcwBpAG8AbgA9ACIANAAuADAALgAwAC4AMAAiAD4APABEAEEAVABBAD4APABQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsARQBZAEwARQBOAD4AMQA2ADwALwBLAEUAWQBMAEUATgA+ADwAQQBMAEcASQBEAD4AQQBFAFMAQwBUAFIAPAAvAEEATABHAEkARAA+ADwALwBQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsASQBEAD4AQQBtAGYAagBDAFQATwBQAGIARQBPAGwAMwBXAEQALwA1AG0AYwBlAGMAQQA9AD0APAAvAEsASQBEAD4APABDAEgARQBDAEsAUwBVAE0APgBCAEcAdwAxAGEAWQBaADEAWQBYAE0APQA8AC8AQwBIAEUAQwBLAFMAVQBNAD4APABMAEEAXwBVAFIATAA+AGgAdAB0AHAAOgAvAC8AcABsAGEAeQByAGUAYQBkAHkALgBkAGkAcgBlAGMAdAB0AGEAcABzAC4AbgBlAHQALwBwAHIALwBzAHYAYwAvAHIAaQBnAGgAdABzAG0AYQBuAGEAZwBlAHIALgBhAHMAbQB4ADwALwBMAEEAXwBVAFIATAA+ADwALwBEAEEAVABBAD4APAAvAFcAUgBNAEgARQBBAEQARQBSAD4A
22 |
23 |
24 | 800k/output-video-1.mp4
25 |
26 |
27 |
28 |
29 |
30 | 1200k/output-video-1.mp4
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | http://example.com/content/sintel/subtitles/subtitles_en.vtt
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/examples/ondemand_with_thumbnails.mpd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | mgIAAAEAAQCQAjwAVwBSAE0ASABFAEEARABFAFIAIAB4AG0AbABuAHMAPQAiAGgAdAB0AHAAOgAvAC8AcwBjAGgAZQBtAGEAcwAuAG0AaQBjAHIAbwBzAG8AZgB0AC4AYwBvAG0ALwBEAFIATQAvADIAMAAwADcALwAwADMALwBQAGwAYQB5AFIAZQBhAGQAeQBIAGUAYQBkAGUAcgAiACAAdgBlAHIAcwBpAG8AbgA9ACIANAAuADAALgAwAC4AMAAiAD4APABEAEEAVABBAD4APABQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsARQBZAEwARQBOAD4AMQA2ADwALwBLAEUAWQBMAEUATgA+ADwAQQBMAEcASQBEAD4AQQBFAFMAQwBUAFIAPAAvAEEATABHAEkARAA+ADwALwBQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsASQBEAD4AQQBtAGYAagBDAFQATwBQAGIARQBPAGwAMwBXAEQALwA1AG0AYwBlAGMAQQA9AD0APAAvAEsASQBEAD4APABDAEgARQBDAEsAUwBVAE0APgBCAEcAdwAxAGEAWQBaADEAWQBYAE0APQA8AC8AQwBIAEUAQwBLAFMAVQBNAD4APABMAEEAXwBVAFIATAA+AGgAdAB0AHAAOgAvAC8AcABsAGEAeQByAGUAYQBkAHkALgBkAGkAcgBlAGMAdAB0AGEAcABzAC4AbgBlAHQALwBwAHIALwBzAHYAYwAvAHIAaQBnAGgAdABzAG0AYQBuAGEAZwBlAHIALgBhAHMAbQB4ADwALwBMAEEAXwBVAFIATAA+ADwALwBEAEEAVABBAD4APAAvAFcAUgBNAEgARQBBAEQARQBSAD4A
9 |
10 |
11 | 800k/output-audio-und.mp4
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | mgIAAAEAAQCQAjwAVwBSAE0ASABFAEEARABFAFIAIAB4AG0AbABuAHMAPQAiAGgAdAB0AHAAOgAvAC8AcwBjAGgAZQBtAGEAcwAuAG0AaQBjAHIAbwBzAG8AZgB0AC4AYwBvAG0ALwBEAFIATQAvADIAMAAwADcALwAwADMALwBQAGwAYQB5AFIAZQBhAGQAeQBIAGUAYQBkAGUAcgAiACAAdgBlAHIAcwBpAG8AbgA9ACIANAAuADAALgAwAC4AMAAiAD4APABEAEEAVABBAD4APABQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsARQBZAEwARQBOAD4AMQA2ADwALwBLAEUAWQBMAEUATgA+ADwAQQBMAEcASQBEAD4AQQBFAFMAQwBUAFIAPAAvAEEATABHAEkARAA+ADwALwBQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsASQBEAD4AQQBtAGYAagBDAFQATwBQAGIARQBPAGwAMwBXAEQALwA1AG0AYwBlAGMAQQA9AD0APAAvAEsASQBEAD4APABDAEgARQBDAEsAUwBVAE0APgBCAEcAdwAxAGEAWQBaADEAWQBYAE0APQA8AC8AQwBIAEUAQwBLAFMAVQBNAD4APABMAEEAXwBVAFIATAA+AGgAdAB0AHAAOgAvAC8AcABsAGEAeQByAGUAYQBkAHkALgBkAGkAcgBlAGMAdAB0AGEAcABzAC4AbgBlAHQALwBwAHIALwBzAHYAYwAvAHIAaQBnAGgAdABzAG0AYQBuAGEAZwBlAHIALgBhAHMAbQB4ADwALwBMAEEAXwBVAFIATAA+ADwALwBEAEEAVABBAD4APAAvAFcAUgBNAEgARQBBAEQARQBSAD4A
22 |
23 |
24 | 800k/output-video-1.mp4
25 |
26 |
27 |
28 |
29 |
30 | 1200k/output-video-1.mp4
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | http://example.com/content/sintel/subtitles/subtitles_en.vtt
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/zencoder/go-dash/v3
2 |
3 | go 1.23
4 |
5 | toolchain go1.23.2
6 |
7 | require github.com/Comcast/scte35-go v1.4.6
8 |
9 | require (
10 | github.com/bamiaux/iobit v0.0.0-20170418073505-498159a04883 // indirect
11 | golang.org/x/text v0.16.0 // indirect
12 | )
13 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/Comcast/scte35-go v1.4.6 h1:jhYrK8xsKjaEf/JmSVGaz0xYrlsUgyjsYYCRY7TGBy4=
2 | github.com/Comcast/scte35-go v1.4.6/go.mod h1:556vqw++rwYrL+GcVH6iX/gaPSDCv9LlNdKAlqpN/ZY=
3 | github.com/bamiaux/iobit v0.0.0-20170418073505-498159a04883 h1:XNtOMwxmV2PI/vuTHDZnFzGIFNUh8MK73q7+Kna7AXs=
4 | github.com/bamiaux/iobit v0.0.0-20170418073505-498159a04883/go.mod h1:9IjZnSQGh45J46HHS45pxuMJ6WFTtSXbaX0FoHDvxh8=
5 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
6 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
7 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
8 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
9 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
10 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
11 | golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
12 | golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
13 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
14 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
15 |
--------------------------------------------------------------------------------
/helpers/ptrs/ptrs.go:
--------------------------------------------------------------------------------
1 | package ptrs
2 |
3 | func Strptr(v string) *string {
4 | p := new(string)
5 | *p = v
6 | return p
7 | }
8 |
9 | func Intptr(v int) *int {
10 | p := new(int)
11 | *p = v
12 | return p
13 | }
14 |
15 | func Int64ptr(v int64) *int64 {
16 | p := new(int64)
17 | *p = v
18 | return p
19 | }
20 |
21 | func Uintptr(v uint) *uint {
22 | p := new(uint)
23 | *p = v
24 | return p
25 | }
26 |
27 | func Uint32ptr(v uint32) *uint32 {
28 | p := new(uint32)
29 | *p = v
30 | return p
31 | }
32 |
33 | func Uint64ptr(v uint64) *uint64 {
34 | p := new(uint64)
35 | *p = v
36 | return p
37 | }
38 |
39 | func Boolptr(v bool) *bool {
40 | p := new(bool)
41 | *p = v
42 | return p
43 | }
44 |
45 | func Float64ptr(v float64) *float64 {
46 | p := new(float64)
47 | *p = v
48 | return p
49 | }
50 |
--------------------------------------------------------------------------------
/helpers/require/require.go:
--------------------------------------------------------------------------------
1 | package require
2 |
3 | import (
4 | "reflect"
5 | "testing"
6 | )
7 |
8 | func NoError(t *testing.T, err error, msgs ...string) {
9 | if err != nil {
10 | t.Errorf("Expected no error, but got: %s", err)
11 | for _, msg := range msgs {
12 | t.Errorf("\n" + msg)
13 | }
14 | t.FailNow()
15 | }
16 | }
17 |
18 | func NotNil(t *testing.T, object interface{}, msgs ...string) {
19 | if isNil(object) {
20 | t.Errorf("Expected nil, but got an object")
21 | for _, msg := range msgs {
22 | t.Errorf("\n" + msg)
23 | }
24 | t.FailNow()
25 | }
26 | }
27 |
28 | func Nil(t *testing.T, object interface{}, msgs ...string) {
29 | if !isNil(object) {
30 | t.Errorf("Expected an object, but got nil")
31 | for _, msg := range msgs {
32 | t.Errorf("\n" + msg)
33 | }
34 | t.FailNow()
35 | }
36 | }
37 |
38 | func isNil(object interface{}) bool {
39 | if object == nil {
40 | return true
41 | }
42 |
43 | value := reflect.ValueOf(object)
44 | kind := value.Kind()
45 | if kind >= reflect.Chan && kind <= reflect.Slice && value.IsNil() {
46 | return true
47 | }
48 |
49 | return false
50 | }
51 |
52 | func EqualError(t *testing.T, theError error, errString string, msgs ...string) {
53 | if theError == nil {
54 | t.Errorf("Expected an error but got nil")
55 | for _, msg := range msgs {
56 | t.Errorf("\n" + msg)
57 | }
58 | t.FailNow()
59 | return
60 | }
61 |
62 | if theError.Error() != errString {
63 | t.Errorf("Expected an error with message:\n%qbut got \n%q", errString, theError)
64 | for _, msg := range msgs {
65 | t.Errorf("\n" + msg)
66 | }
67 | t.FailNow()
68 | }
69 | }
70 |
71 | func EqualFloat64(t *testing.T, expected, actual float64, msgs ...string) {
72 | if expected != actual {
73 | t.Errorf("Expected %f but got %f", expected, actual)
74 | for _, msg := range msgs {
75 | t.Errorf("\n" + msg)
76 | }
77 | t.FailNow()
78 | }
79 | }
80 |
81 | func EqualStringPtr(t *testing.T, expected, actual *string, msgs ...string) {
82 | if expected == nil && actual == nil {
83 | return
84 | }
85 | if expected != nil && actual != nil {
86 | EqualString(t, *expected, *actual, msgs...)
87 | return
88 | }
89 |
90 | t.Errorf("Expected %v but got %v", expected, actual)
91 | for _, msg := range msgs {
92 | t.Errorf("\n" + msg)
93 | }
94 | t.FailNow()
95 | }
96 |
97 | func EqualString(t *testing.T, expected, actual string, msgs ...string) {
98 | if expected != actual {
99 | t.Errorf("Expected %s but got %s", expected, actual)
100 | for _, msg := range msgs {
101 | t.Errorf("\n" + msg)
102 | }
103 | t.FailNow()
104 | }
105 | }
106 |
107 | func EqualStringSlice(t *testing.T, expected, actual []string, msgs ...string) {
108 | if len(expected) != len(actual) {
109 | t.Errorf("Expected %v but got %v", expected, actual)
110 | for _, msg := range msgs {
111 | t.Errorf("\n" + msg)
112 | }
113 | t.FailNow()
114 | }
115 | for i, e := range expected {
116 | a := actual[i]
117 | EqualString(t, e, a, msgs...)
118 | }
119 | }
120 |
121 | func EqualUInt32(t *testing.T, expected, actual uint32, msgs ...string) {
122 | if expected != actual {
123 | t.Errorf("Expected %d but got %d", expected, actual)
124 | for _, msg := range msgs {
125 | t.Errorf("\n" + msg)
126 | }
127 | t.FailNow()
128 | }
129 | }
130 |
131 | func EqualUInt64Ptr(t *testing.T, expected, actual *uint64, msgs ...string) {
132 | if expected == nil && actual == nil {
133 | return
134 | }
135 | if expected != nil && actual != nil {
136 | EqualUInt64(t, *expected, *actual, msgs...)
137 | return
138 | }
139 |
140 | t.Errorf("Expected %v but got %v", expected, actual)
141 | for _, msg := range msgs {
142 | t.Errorf("\n" + msg)
143 | }
144 | t.FailNow()
145 | }
146 |
147 | func EqualUInt64(t *testing.T, expected, actual uint64, msgs ...string) {
148 | if expected != actual {
149 | t.Errorf("Expected %d but got %d", expected, actual)
150 | for _, msg := range msgs {
151 | t.Errorf("\n" + msg)
152 | }
153 | t.FailNow()
154 | }
155 | }
156 |
157 | func EqualIntPtr(t *testing.T, expected, actual *int, msgs ...string) {
158 | if expected == nil && actual == nil {
159 | return
160 | }
161 | if expected != nil && actual != nil {
162 | EqualInt(t, *expected, *actual, msgs...)
163 | return
164 | }
165 |
166 | t.Errorf("Expected %v but got %v", expected, actual)
167 | for _, msg := range msgs {
168 | t.Errorf("\n" + msg)
169 | }
170 | t.FailNow()
171 | }
172 |
173 | func EqualInt(t *testing.T, expected, actual int, msgs ...string) {
174 | if expected != actual {
175 | t.Errorf("Expected %d but got %d", expected, actual)
176 | for _, msg := range msgs {
177 | t.Errorf("\n" + msg)
178 | }
179 | t.FailNow()
180 | }
181 | }
182 |
183 | func EqualErr(t *testing.T, expected, actual error, msgs ...string) {
184 | if expected != actual {
185 | t.Errorf("Expected %s but got %s", expected, actual)
186 | for _, msg := range msgs {
187 | t.Errorf("\n" + msg)
188 | }
189 | t.FailNow()
190 | }
191 | }
192 |
193 | func Implements(t *testing.T, interfaceObject interface{}, object interface{}, msgs ...string) {
194 | interfaceType := reflect.TypeOf(interfaceObject).Elem()
195 |
196 | if !reflect.TypeOf(object).Implements(interfaceType) {
197 | t.Errorf("Expected %T but got %v", object, interfaceType)
198 | for _, msg := range msgs {
199 | t.Errorf("\n" + msg)
200 | }
201 | t.FailNow()
202 | }
203 | }
204 |
--------------------------------------------------------------------------------
/helpers/testfixtures/testfixtures.go:
--------------------------------------------------------------------------------
1 | package testfixtures
2 |
3 | import (
4 | "fmt"
5 | "io/ioutil"
6 | "os"
7 | "testing"
8 |
9 | "github.com/zencoder/go-dash/v3/helpers/require"
10 | )
11 |
12 | // Load test fixture from path relative to fixtures directory
13 | func LoadFixture(path string) (js string) {
14 | f, err := ioutil.ReadFile(path)
15 | if err != nil {
16 | panic(fmt.Sprintf("LoadFixture Error. ioutil.ReadFile. path = %s, Err = %s", path, err.Error()))
17 | }
18 | return string(f)
19 | }
20 |
21 | func CompareFixture(t *testing.T, fixturePath string, actualContent string) {
22 | expectedContent := LoadFixture(fixturePath)
23 | if os.Getenv("GENERATE_FIXTURES") != "" {
24 | _ = ioutil.WriteFile(fixturePath, []byte(actualContent), os.ModePerm)
25 | fmt.Println("Wrote fixture: " + fixturePath)
26 | return
27 | }
28 | require.EqualString(t, expectedContent, actualContent)
29 | }
30 |
--------------------------------------------------------------------------------
/mpd/duration.go:
--------------------------------------------------------------------------------
1 | package mpd
2 |
3 | import (
4 | "encoding/xml"
5 | "errors"
6 | "fmt"
7 | "regexp"
8 | "strconv"
9 | "strings"
10 | "time"
11 | )
12 |
13 | // Duration is an extension of the original time.Duration. This type is used to
14 | // re-format the String() output to support the ISO 8601 duration standard. And
15 | // add the MarshalXMLAttr and UnmarshalXMLAttr functions.
16 | type Duration time.Duration
17 |
18 | var (
19 | rStart = "^P" // Must start with a 'P'
20 | rDays = "(\\d+D)?" // We only allow Days for durations, not Months or Years
21 | rTime = "(?:T" // If there's any 'time' units then they must be preceded by a 'T'
22 | rHours = "(\\d+H)?" // Hours
23 | rMinutes = "(\\d+M)?" // Minutes
24 | rSeconds = "([\\d.]+S)?" // Seconds (Potentially decimal)
25 | rEnd = ")?$" // end of regex must close "T" capture group
26 | )
27 |
28 | var xmlDurationRegex = regexp.MustCompile(rStart + rDays + rTime + rHours + rMinutes + rSeconds + rEnd)
29 |
30 | func (d *Duration) MarshalXMLAttr(name xml.Name) (xml.Attr, error) {
31 | return xml.Attr{Name: name, Value: d.String()}, nil
32 | }
33 |
34 | func (d *Duration) UnmarshalXMLAttr(attr xml.Attr) error {
35 | dur, err := ParseDuration(attr.Value)
36 | if err != nil {
37 | return err
38 | }
39 | *d = Duration(dur)
40 | return nil
41 | }
42 |
43 | // String returns a string representing the duration in the form "PT72H3M0.5S".
44 | // Leading zero units are omitted. The zero duration formats as PT0S.
45 | // Based on src/time/time.go's time.Duration.String function.
46 | func (d *Duration) String() string {
47 | // This is inlinable to take advantage of "function outlining".
48 | // Thus, the caller can decide whether a string must be heap allocated.
49 | var arr [32]byte
50 |
51 | if d == nil {
52 | return "PT0S"
53 | }
54 |
55 | n := d.format(&arr)
56 | return "PT" + string(arr[n:])
57 | }
58 |
59 | // format formats the representation of d into the end of buf and returns the
60 | // offset of the first character. This function is modified to use the iso 1801
61 | // duration standard. This standard only uses the "H", "M", "S" characters.
62 | // // Based on src/time/time.go's time.Duration.Format function.
63 | func (d *Duration) format(buf *[32]byte) int {
64 | // Largest time is 2540400h10m10.000000000s
65 | w := len(buf)
66 |
67 | u := uint64(*d)
68 | neg := *d < 0
69 | if neg {
70 | u = -u
71 | }
72 |
73 | w--
74 | buf[w] = 'S'
75 |
76 | w, u = fmtFrac(buf[:w], u, 9)
77 |
78 | // u is now integer seconds
79 | w = fmtInt(buf[:w], u%60)
80 | u /= 60
81 |
82 | // u is now integer minutes
83 | if u > 0 {
84 | w--
85 | buf[w] = 'M'
86 | w = fmtInt(buf[:w], u%60)
87 | u /= 60
88 |
89 | // u is now integer hours
90 | // Stop at hours because days can be different lengths.
91 | if u > 0 {
92 | w--
93 | buf[w] = 'H'
94 | w = fmtInt(buf[:w], u)
95 | }
96 | }
97 |
98 | if neg {
99 | w--
100 | buf[w] = '-'
101 | }
102 |
103 | return w
104 | }
105 |
106 | // fmtFrac formats the fraction of v/10**prec (e.g., ".12345") into the
107 | // tail of buf, omitting trailing zeros. it omits the decimal
108 | // point too when the fraction is 0. It returns the index where the
109 | // output bytes begin and the value v/10**prec.
110 | // Copied from src/time/time.go.
111 | func fmtFrac(buf []byte, v uint64, prec int) (nw int, nv uint64) {
112 | // Omit trailing zeros up to and including decimal point.
113 | w := len(buf)
114 | print := false
115 | for i := 0; i < prec; i++ {
116 | digit := v % 10
117 | print = print || digit != 0
118 | if print {
119 | w--
120 | buf[w] = byte(digit) + '0'
121 | }
122 | v /= 10
123 | }
124 | if print {
125 | w--
126 | buf[w] = '.'
127 | }
128 | return w, v
129 | }
130 |
131 | // fmtInt formats v into the tail of buf.
132 | // It returns the index where the output begins.
133 | // Copied from src/time/time.go.
134 | func fmtInt(buf []byte, v uint64) int {
135 | w := len(buf)
136 | if v == 0 {
137 | w--
138 | buf[w] = '0'
139 | } else {
140 | for v > 0 {
141 | w--
142 | buf[w] = byte(v%10) + '0'
143 | v /= 10
144 | }
145 | }
146 | return w
147 | }
148 |
149 | func ParseDuration(str string) (time.Duration, error) {
150 | if len(str) < 3 {
151 | return 0, errors.New("at least one number and designator are required")
152 | }
153 |
154 | if strings.Contains(str, "-") {
155 | return 0, errors.New("duration cannot be negative")
156 | }
157 |
158 | // Check that only the parts we expect exist and that everything's in the correct order
159 | if !xmlDurationRegex.Match([]byte(str)) {
160 | return 0, errors.New("duration must be in the format: P[nD][T[nH][nM][nS]]")
161 | }
162 |
163 | var parts = xmlDurationRegex.FindStringSubmatch(str)
164 | var total time.Duration
165 |
166 | if parts[1] != "" {
167 | days, err := strconv.Atoi(strings.TrimRight(parts[1], "D"))
168 | if err != nil {
169 | return 0, fmt.Errorf("error parsing Days: %s", err)
170 | }
171 | total += time.Duration(days) * time.Hour * 24
172 | }
173 |
174 | if parts[2] != "" {
175 | hours, err := strconv.Atoi(strings.TrimRight(parts[2], "H"))
176 | if err != nil {
177 | return 0, fmt.Errorf("error parsing Hours: %s", err)
178 | }
179 | total += time.Duration(hours) * time.Hour
180 | }
181 |
182 | if parts[3] != "" {
183 | mins, err := strconv.Atoi(strings.TrimRight(parts[3], "M"))
184 | if err != nil {
185 | return 0, fmt.Errorf("error parsing Minutes: %s", err)
186 | }
187 | total += time.Duration(mins) * time.Minute
188 | }
189 |
190 | if parts[4] != "" {
191 | secs, err := strconv.ParseFloat(strings.TrimRight(parts[4], "S"), 64)
192 | if err != nil {
193 | return 0, fmt.Errorf("error parsing Seconds: %s", err)
194 | }
195 | total += time.Duration(secs * float64(time.Second))
196 | }
197 |
198 | return total, nil
199 | }
200 |
--------------------------------------------------------------------------------
/mpd/duration_test.go:
--------------------------------------------------------------------------------
1 | package mpd
2 |
3 | import (
4 | "fmt"
5 | "testing"
6 | "time"
7 |
8 | "github.com/zencoder/go-dash/v3/helpers/require"
9 | )
10 |
11 | func TestDuration(t *testing.T) {
12 | in := map[string]string{
13 | "0.5ms": "PT0.0005S",
14 | "7ms": "PT0.007S",
15 | "0s": "PT0S",
16 | "6m16s": "PT6M16S",
17 | "1.97s": "PT1.97S",
18 | "4988000000ns": "PT4.988S",
19 | "2h4m30s7ms": "PT2H4M30.007S",
20 | }
21 | for ins, ex := range in {
22 | t.Run(ins, func(t *testing.T) {
23 | timeDur, err := time.ParseDuration(ins)
24 | require.NoError(t, err)
25 | dur := Duration(timeDur)
26 | require.EqualString(t, ex, dur.String())
27 | })
28 | }
29 | }
30 |
31 | func TestParseDuration(t *testing.T) {
32 | in := map[string]float64{
33 | "PT0S": 0,
34 | "PT1M": 60,
35 | "PT2H": 7200,
36 | "PT6M16S": 376,
37 | "PT1.97S": 1.97,
38 | "PT1H2M3.456S": 3723.456,
39 | "P1DT2H": (26 * time.Hour).Seconds(),
40 | "PT20M": (20 * time.Minute).Seconds(),
41 | "PT1M30.5S": (time.Minute + 30*time.Second + 500*time.Millisecond).Seconds(),
42 | "PT1004199059S": (1004199059 * time.Second).Seconds(),
43 | }
44 | for ins, ex := range in {
45 | t.Run(ins, func(t *testing.T) {
46 | act, err := ParseDuration(ins)
47 | require.NoError(t, err, ins)
48 | require.EqualFloat64(t, ex, act.Seconds(), ins)
49 | })
50 | }
51 | }
52 |
53 | func TestParseBadDurations(t *testing.T) {
54 | in := map[string]string{
55 | "P20M": `duration must be in the format: P[nD][T[nH][nM][nS]]`, // We don't allow Months (doesn't make sense when converting to duration)
56 | "P20Y": `duration must be in the format: P[nD][T[nH][nM][nS]]`, // We don't allow Years (doesn't make sense when converting to duration)
57 | "P15.5D": `duration must be in the format: P[nD][T[nH][nM][nS]]`, // Only seconds can be expressed as a decimal
58 | "P2H": `duration must be in the format: P[nD][T[nH][nM][nS]]`, // "T" must be present to separate days and hours
59 | "2DT1H": `duration must be in the format: P[nD][T[nH][nM][nS]]`, // "P" must always be present
60 | "PT2M1H": `duration must be in the format: P[nD][T[nH][nM][nS]]`, // Hours must appear before Minutes
61 | "P": `at least one number and designator are required`, // At least one number and designator are required
62 | "-P20H": `duration cannot be negative`, // Negative duration doesn't make sense
63 | }
64 | for ins, msg := range in {
65 | t.Run(ins, func(t *testing.T) {
66 | _, err := ParseDuration(ins)
67 | require.EqualError(t, err, msg, fmt.Sprintf("Expected an error for: %s", ins))
68 | })
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/mpd/events.go:
--------------------------------------------------------------------------------
1 | package mpd
2 |
3 | import "encoding/xml"
4 |
5 | type EventStream struct {
6 | XMLName xml.Name `xml:"EventStream"`
7 | SchemeIDURI *string `xml:"schemeIdUri,attr"`
8 | Value *string `xml:"value,attr,omitempty"`
9 | Timescale *uint `xml:"timescale,attr"`
10 | Events []Event `xml:"Event,omitempty"`
11 | }
12 |
13 | type Event struct {
14 | XMLName xml.Name `xml:"Event"`
15 | ID *string `xml:"id,attr,omitempty"`
16 | PresentationTime *uint64 `xml:"presentationTime,attr,omitempty"`
17 | Duration *uint64 `xml:"duration,attr,omitempty"`
18 | Signals []Signal `xml:"Signal,omitempty"`
19 | }
20 |
21 | type ByPresentationTime []Event
22 |
23 | func (p ByPresentationTime) Len() int {
24 | return len(p)
25 | }
26 |
27 | func (p ByPresentationTime) Less(i, j int) bool {
28 | return *p[i].PresentationTime < *p[j].PresentationTime
29 | }
30 |
31 | func (p ByPresentationTime) Swap(i, j int) {
32 | p[i], p[j] = p[j], p[i]
33 | }
34 |
--------------------------------------------------------------------------------
/mpd/events_test.go:
--------------------------------------------------------------------------------
1 | package mpd
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/zencoder/go-dash/v3/helpers/ptrs"
7 | "github.com/zencoder/go-dash/v3/helpers/require"
8 | "github.com/zencoder/go-dash/v3/helpers/testfixtures"
9 | )
10 |
11 | const (
12 | VALID_EVENT_STREAM_SCHEME_ID_URI = "urn:example:eventstream"
13 | VALID_EVENT_STREAM_VALUE = "eventstream"
14 | VALID_EVENT_STREAM_TIMESCALE uint = 10
15 | )
16 |
17 | func newEventStreamMPD() *MPD {
18 | m := NewDynamicMPD(
19 | DASH_PROFILE_LIVE,
20 | VALID_AVAILABILITY_START_TIME,
21 | VALID_MIN_BUFFER_TIME,
22 | )
23 | p := m.GetCurrentPeriod()
24 |
25 | es := EventStream{
26 | SchemeIDURI: ptrs.Strptr(VALID_EVENT_STREAM_SCHEME_ID_URI),
27 | Value: ptrs.Strptr(VALID_EVENT_STREAM_VALUE),
28 | Timescale: ptrs.Uintptr(VALID_EVENT_STREAM_TIMESCALE),
29 | }
30 |
31 | e0 := Event{
32 | ID: ptrs.Strptr("event-0"),
33 | PresentationTime: ptrs.Uint64ptr(100),
34 | Duration: ptrs.Uint64ptr(50),
35 | }
36 |
37 | e1 := Event{
38 | ID: ptrs.Strptr("event-1"),
39 | PresentationTime: ptrs.Uint64ptr(200),
40 | Duration: ptrs.Uint64ptr(50),
41 | }
42 |
43 | es.Events = append(es.Events, e0, e1)
44 | p.EventStreams = append(p.EventStreams, es)
45 |
46 | return m
47 | }
48 |
49 | func TestEventStreamsWriteToString(t *testing.T) {
50 | m := newEventStreamMPD()
51 |
52 | got, err := m.WriteToString()
53 | require.NoError(t, err)
54 |
55 | testfixtures.CompareFixture(t, "fixtures/events.mpd", got)
56 | }
57 |
58 | func TestReadEventStreams(t *testing.T) {
59 | m, err := ReadFromFile("fixtures/events.mpd")
60 | require.NoError(t, err)
61 |
62 | got, err := m.WriteToString()
63 | require.NoError(t, err)
64 |
65 | testfixtures.CompareFixture(t, "fixtures/events.mpd", got)
66 | }
67 |
--------------------------------------------------------------------------------
/mpd/fixtures/adaptationset_switching.mpd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | http://localhost:8002/dash/
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | AAAAYXBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAAEEIARIQWr3VL1VKTyq40GH3YUJRVRoIY2FzdGxhYnMiGFdyM1ZMMVZLVHlxNDBHSDNZVUpSVlE9PTIHZGVmYXVsdA==
18 |
19 |
20 | BgIAAAEAAQD8ATwAVwBSAE0ASABFAEEARABFAFIAIAB4AG0AbABuAHMAPQAiAGgAdAB0AHAAOgAvAC8AcwBjAGgAZQBtAGEAcwAuAG0AaQBjAHIAbwBzAG8AZgB0AC4AYwBvAG0ALwBEAFIATQAvADIAMAAwADcALwAwADMALwBQAGwAYQB5AFIAZQBhAGQAeQBIAGUAYQBkAGUAcgAiACAAdgBlAHIAcwBpAG8AbgA9ACIANAAuADAALgAwAC4AMAAiAD4APABEAEEAVABBAD4APABQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsARQBZAEwARQBOAD4AMQA2ADwALwBLAEUAWQBMAEUATgA+ADwAQQBMAEcASQBEAD4AQQBFAFMAQwBUAFIAPAAvAEEATABHAEkARAA+ADwALwBQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsASQBEAD4ATAA5AFcAOQBXAGsAcABWAEsAawArADQAMABHAEgAMwBZAFUASgBSAFYAUQA9AD0APAAvAEsASQBEAD4APABDAEgARQBDAEsAUwBVAE0APgBJAEsAegBZADIASABaAEwAQQBsAEkAPQA8AC8AQwBIAEUAQwBLAFMAVQBNAD4APAAvAEQAQQBUAEEAPgA8AC8AVwBSAE0ASABFAEEARABFAFIAPgA=
21 | AAACJnBzc2gAAAAAmgTweZhAQoarkuZb4IhflQAAAgYGAgAAAQABAPwBPABXAFIATQBIAEUAQQBEAEUAUgAgAHgAbQBsAG4AcwA9ACIAaAB0AHQAcAA6AC8ALwBzAGMAaABlAG0AYQBzAC4AbQBpAGMAcgBvAHMAbwBmAHQALgBjAG8AbQAvAEQAUgBNAC8AMgAwADAANwAvADAAMwAvAFAAbABhAHkAUgBlAGEAZAB5AEgAZQBhAGQAZQByACIAIAB2AGUAcgBzAGkAbwBuAD0AIgA0AC4AMAAuADAALgAwACIAPgA8AEQAQQBUAEEAPgA8AFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBFAFkATABFAE4APgAxADYAPAAvAEsARQBZAEwARQBOAD4APABBAEwARwBJAEQAPgBBAEUAUwBDAFQAUgA8AC8AQQBMAEcASQBEAD4APAAvAFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBJAEQAPgBMADkAVwA5AFcAawBwAFYASwBrACsANAAwAEcASAAzAFkAVQBKAFIAVgBRAD0APQA8AC8ASwBJAEQAPgA8AEMASABFAEMASwBTAFUATQA+AEkASwB6AFkAMgBIAFoATABBAGwASQA9ADwALwBDAEgARQBDAEsAUwBVAE0APgA8AC8ARABBAFQAQQA+ADwALwBXAFIATQBIAEUAQQBEAEUAUgA+AA==
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | AAAAYXBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAAEEIARIQWr3VL1VKTyq40GH3YUJRVRoIY2FzdGxhYnMiGFdyM1ZMMVZLVHlxNDBHSDNZVUpSVlE9PTIHZGVmYXVsdA==
35 |
36 |
37 | BgIAAAEAAQD8ATwAVwBSAE0ASABFAEEARABFAFIAIAB4AG0AbABuAHMAPQAiAGgAdAB0AHAAOgAvAC8AcwBjAGgAZQBtAGEAcwAuAG0AaQBjAHIAbwBzAG8AZgB0AC4AYwBvAG0ALwBEAFIATQAvADIAMAAwADcALwAwADMALwBQAGwAYQB5AFIAZQBhAGQAeQBIAGUAYQBkAGUAcgAiACAAdgBlAHIAcwBpAG8AbgA9ACIANAAuADAALgAwAC4AMAAiAD4APABEAEEAVABBAD4APABQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsARQBZAEwARQBOAD4AMQA2ADwALwBLAEUAWQBMAEUATgA+ADwAQQBMAEcASQBEAD4AQQBFAFMAQwBUAFIAPAAvAEEATABHAEkARAA+ADwALwBQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsASQBEAD4ATAA5AFcAOQBXAGsAcABWAEsAawArADQAMABHAEgAMwBZAFUASgBSAFYAUQA9AD0APAAvAEsASQBEAD4APABDAEgARQBDAEsAUwBVAE0APgBJAEsAegBZADIASABaAEwAQQBsAEkAPQA8AC8AQwBIAEUAQwBLAFMAVQBNAD4APAAvAEQAQQBUAEEAPgA8AC8AVwBSAE0ASABFAEEARABFAFIAPgA=
38 | AAACJnBzc2gAAAAAmgTweZhAQoarkuZb4IhflQAAAgYGAgAAAQABAPwBPABXAFIATQBIAEUAQQBEAEUAUgAgAHgAbQBsAG4AcwA9ACIAaAB0AHQAcAA6AC8ALwBzAGMAaABlAG0AYQBzAC4AbQBpAGMAcgBvAHMAbwBmAHQALgBjAG8AbQAvAEQAUgBNAC8AMgAwADAANwAvADAAMwAvAFAAbABhAHkAUgBlAGEAZAB5AEgAZQBhAGQAZQByACIAIAB2AGUAcgBzAGkAbwBuAD0AIgA0AC4AMAAuADAALgAwACIAPgA8AEQAQQBUAEEAPgA8AFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBFAFkATABFAE4APgAxADYAPAAvAEsARQBZAEwARQBOAD4APABBAEwARwBJAEQAPgBBAEUAUwBDAFQAUgA8AC8AQQBMAEcASQBEAD4APAAvAFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBJAEQAPgBMADkAVwA5AFcAawBwAFYASwBrACsANAAwAEcASAAzAFkAVQBKAFIAVgBRAD0APQA8AC8ASwBJAEQAPgA8AEMASABFAEMASwBTAFUATQA+AEkASwB6AFkAMgBIAFoATABBAGwASQA9ADwALwBDAEgARQBDAEsAUwBVAE0APgA8AC8ARABBAFQAQQA+ADwALwBXAFIATQBIAEUAQQBEAEUAUgA+AA==
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/mpd/fixtures/audio_channel_configuration.mpd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | http://localhost:8002/dash/
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/mpd/fixtures/events.mpd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/mpd/fixtures/hbbtv_profile.mpd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | AAAAYXBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAAEEIARIQWr3VL1VKTyq40GH3YUJRVRoIY2FzdGxhYnMiGFdyM1ZMMVZLVHlxNDBHSDNZVUpSVlE9PTIHZGVmYXVsdA==
8 |
9 |
10 | BgIAAAEAAQD8ATwAVwBSAE0ASABFAEEARABFAFIAIAB4AG0AbABuAHMAPQAiAGgAdAB0AHAAOgAvAC8AcwBjAGgAZQBtAGEAcwAuAG0AaQBjAHIAbwBzAG8AZgB0AC4AYwBvAG0ALwBEAFIATQAvADIAMAAwADcALwAwADMALwBQAGwAYQB5AFIAZQBhAGQAeQBIAGUAYQBkAGUAcgAiACAAdgBlAHIAcwBpAG8AbgA9ACIANAAuADAALgAwAC4AMAAiAD4APABEAEEAVABBAD4APABQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsARQBZAEwARQBOAD4AMQA2ADwALwBLAEUAWQBMAEUATgA+ADwAQQBMAEcASQBEAD4AQQBFAFMAQwBUAFIAPAAvAEEATABHAEkARAA+ADwALwBQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsASQBEAD4ATAA5AFcAOQBXAGsAcABWAEsAawArADQAMABHAEgAMwBZAFUASgBSAFYAUQA9AD0APAAvAEsASQBEAD4APABDAEgARQBDAEsAUwBVAE0APgBJAEsAegBZADIASABaAEwAQQBsAEkAPQA8AC8AQwBIAEUAQwBLAFMAVQBNAD4APAAvAEQAQQBUAEEAPgA8AC8AVwBSAE0ASABFAEEARABFAFIAPgA=
11 | AAACJnBzc2gAAAAAmgTweZhAQoarkuZb4IhflQAAAgYGAgAAAQABAPwBPABXAFIATQBIAEUAQQBEAEUAUgAgAHgAbQBsAG4AcwA9ACIAaAB0AHQAcAA6AC8ALwBzAGMAaABlAG0AYQBzAC4AbQBpAGMAcgBvAHMAbwBmAHQALgBjAG8AbQAvAEQAUgBNAC8AMgAwADAANwAvADAAMwAvAFAAbABhAHkAUgBlAGEAZAB5AEgAZQBhAGQAZQByACIAIAB2AGUAcgBzAGkAbwBuAD0AIgA0AC4AMAAuADAALgAwACIAPgA8AEQAQQBUAEEAPgA8AFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBFAFkATABFAE4APgAxADYAPAAvAEsARQBZAEwARQBOAD4APABBAEwARwBJAEQAPgBBAEUAUwBDAFQAUgA8AC8AQQBMAEcASQBEAD4APAAvAFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBJAEQAPgBMADkAVwA5AFcAawBwAFYASwBrACsANAAwAEcASAAzAFkAVQBKAFIAVgBRAD0APQA8AC8ASwBJAEQAPgA8AEMASABFAEMASwBTAFUATQA+AEkASwB6AFkAMgBIAFoATABBAGwASQA9ADwALwBDAEgARQBDAEsAUwBVAE0APgA8AC8ARABBAFQAQQA+ADwALwBXAFIATQBIAEUAQQBEAEUAUgA+AA==
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | AAAAYXBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAAEEIARIQWr3VL1VKTyq40GH3YUJRVRoIY2FzdGxhYnMiGFdyM1ZMMVZLVHlxNDBHSDNZVUpSVlE9PTIHZGVmYXVsdA==
24 |
25 |
26 | BgIAAAEAAQD8ATwAVwBSAE0ASABFAEEARABFAFIAIAB4AG0AbABuAHMAPQAiAGgAdAB0AHAAOgAvAC8AcwBjAGgAZQBtAGEAcwAuAG0AaQBjAHIAbwBzAG8AZgB0AC4AYwBvAG0ALwBEAFIATQAvADIAMAAwADcALwAwADMALwBQAGwAYQB5AFIAZQBhAGQAeQBIAGUAYQBkAGUAcgAiACAAdgBlAHIAcwBpAG8AbgA9ACIANAAuADAALgAwAC4AMAAiAD4APABEAEEAVABBAD4APABQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsARQBZAEwARQBOAD4AMQA2ADwALwBLAEUAWQBMAEUATgA+ADwAQQBMAEcASQBEAD4AQQBFAFMAQwBUAFIAPAAvAEEATABHAEkARAA+ADwALwBQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsASQBEAD4ATAA5AFcAOQBXAGsAcABWAEsAawArADQAMABHAEgAMwBZAFUASgBSAFYAUQA9AD0APAAvAEsASQBEAD4APABDAEgARQBDAEsAUwBVAE0APgBJAEsAegBZADIASABaAEwAQQBsAEkAPQA8AC8AQwBIAEUAQwBLAFMAVQBNAD4APAAvAEQAQQBUAEEAPgA8AC8AVwBSAE0ASABFAEEARABFAFIAPgA=
27 | AAACJnBzc2gAAAAAmgTweZhAQoarkuZb4IhflQAAAgYGAgAAAQABAPwBPABXAFIATQBIAEUAQQBEAEUAUgAgAHgAbQBsAG4AcwA9ACIAaAB0AHQAcAA6AC8ALwBzAGMAaABlAG0AYQBzAC4AbQBpAGMAcgBvAHMAbwBmAHQALgBjAG8AbQAvAEQAUgBNAC8AMgAwADAANwAvADAAMwAvAFAAbABhAHkAUgBlAGEAZAB5AEgAZQBhAGQAZQByACIAIAB2AGUAcgBzAGkAbwBuAD0AIgA0AC4AMAAuADAALgAwACIAPgA8AEQAQQBUAEEAPgA8AFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBFAFkATABFAE4APgAxADYAPAAvAEsARQBZAEwARQBOAD4APABBAEwARwBJAEQAPgBBAEUAUwBDAFQAUgA8AC8AQQBMAEcASQBEAD4APAAvAFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBJAEQAPgBMADkAVwA5AFcAawBwAFYASwBrACsANAAwAEcASAAzAFkAVQBKAFIAVgBRAD0APQA8AC8ASwBJAEQAPgA8AEMASABFAEMASwBTAFUATQA+AEkASwB6AFkAMgBIAFoATABBAGwASQA9ADwALwBDAEgARQBDAEsAUwBVAE0APgA8AC8ARABBAFQAQQA+ADwALwBXAFIATQBIAEUAQQBEAEUAUgA+AA==
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | http://example.com/content/sintel/subtitles/subtitles_en.vtt
39 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/mpd/fixtures/inband_event_stream.mpd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/mpd/fixtures/invalid.mpd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/mpd/fixtures/live_profile.mpd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | AAAAYXBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAAEEIARIQWr3VL1VKTyq40GH3YUJRVRoIY2FzdGxhYnMiGFdyM1ZMMVZLVHlxNDBHSDNZVUpSVlE9PTIHZGVmYXVsdA==
8 |
9 |
10 | BgIAAAEAAQD8ATwAVwBSAE0ASABFAEEARABFAFIAIAB4AG0AbABuAHMAPQAiAGgAdAB0AHAAOgAvAC8AcwBjAGgAZQBtAGEAcwAuAG0AaQBjAHIAbwBzAG8AZgB0AC4AYwBvAG0ALwBEAFIATQAvADIAMAAwADcALwAwADMALwBQAGwAYQB5AFIAZQBhAGQAeQBIAGUAYQBkAGUAcgAiACAAdgBlAHIAcwBpAG8AbgA9ACIANAAuADAALgAwAC4AMAAiAD4APABEAEEAVABBAD4APABQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsARQBZAEwARQBOAD4AMQA2ADwALwBLAEUAWQBMAEUATgA+ADwAQQBMAEcASQBEAD4AQQBFAFMAQwBUAFIAPAAvAEEATABHAEkARAA+ADwALwBQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsASQBEAD4ATAA5AFcAOQBXAGsAcABWAEsAawArADQAMABHAEgAMwBZAFUASgBSAFYAUQA9AD0APAAvAEsASQBEAD4APABDAEgARQBDAEsAUwBVAE0APgBJAEsAegBZADIASABaAEwAQQBsAEkAPQA8AC8AQwBIAEUAQwBLAFMAVQBNAD4APAAvAEQAQQBUAEEAPgA8AC8AVwBSAE0ASABFAEEARABFAFIAPgA=
11 | AAACJnBzc2gAAAAAmgTweZhAQoarkuZb4IhflQAAAgYGAgAAAQABAPwBPABXAFIATQBIAEUAQQBEAEUAUgAgAHgAbQBsAG4AcwA9ACIAaAB0AHQAcAA6AC8ALwBzAGMAaABlAG0AYQBzAC4AbQBpAGMAcgBvAHMAbwBmAHQALgBjAG8AbQAvAEQAUgBNAC8AMgAwADAANwAvADAAMwAvAFAAbABhAHkAUgBlAGEAZAB5AEgAZQBhAGQAZQByACIAIAB2AGUAcgBzAGkAbwBuAD0AIgA0AC4AMAAuADAALgAwACIAPgA8AEQAQQBUAEEAPgA8AFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBFAFkATABFAE4APgAxADYAPAAvAEsARQBZAEwARQBOAD4APABBAEwARwBJAEQAPgBBAEUAUwBDAFQAUgA8AC8AQQBMAEcASQBEAD4APAAvAFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBJAEQAPgBMADkAVwA5AFcAawBwAFYASwBrACsANAAwAEcASAAzAFkAVQBKAFIAVgBRAD0APQA8AC8ASwBJAEQAPgA8AEMASABFAEMASwBTAFUATQA+AEkASwB6AFkAMgBIAFoATABBAGwASQA9ADwALwBDAEgARQBDAEsAUwBVAE0APgA8AC8ARABBAFQAQQA+ADwALwBXAFIATQBIAEUAQQBEAEUAUgA+AA==
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | AAAAYXBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAAEEIARIQWr3VL1VKTyq40GH3YUJRVRoIY2FzdGxhYnMiGFdyM1ZMMVZLVHlxNDBHSDNZVUpSVlE9PTIHZGVmYXVsdA==
22 |
23 |
24 | BgIAAAEAAQD8ATwAVwBSAE0ASABFAEEARABFAFIAIAB4AG0AbABuAHMAPQAiAGgAdAB0AHAAOgAvAC8AcwBjAGgAZQBtAGEAcwAuAG0AaQBjAHIAbwBzAG8AZgB0AC4AYwBvAG0ALwBEAFIATQAvADIAMAAwADcALwAwADMALwBQAGwAYQB5AFIAZQBhAGQAeQBIAGUAYQBkAGUAcgAiACAAdgBlAHIAcwBpAG8AbgA9ACIANAAuADAALgAwAC4AMAAiAD4APABEAEEAVABBAD4APABQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsARQBZAEwARQBOAD4AMQA2ADwALwBLAEUAWQBMAEUATgA+ADwAQQBMAEcASQBEAD4AQQBFAFMAQwBUAFIAPAAvAEEATABHAEkARAA+ADwALwBQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsASQBEAD4ATAA5AFcAOQBXAGsAcABWAEsAawArADQAMABHAEgAMwBZAFUASgBSAFYAUQA9AD0APAAvAEsASQBEAD4APABDAEgARQBDAEsAUwBVAE0APgBJAEsAegBZADIASABaAEwAQQBsAEkAPQA8AC8AQwBIAEUAQwBLAFMAVQBNAD4APAAvAEQAQQBUAEEAPgA8AC8AVwBSAE0ASABFAEEARABFAFIAPgA=
25 | AAACJnBzc2gAAAAAmgTweZhAQoarkuZb4IhflQAAAgYGAgAAAQABAPwBPABXAFIATQBIAEUAQQBEAEUAUgAgAHgAbQBsAG4AcwA9ACIAaAB0AHQAcAA6AC8ALwBzAGMAaABlAG0AYQBzAC4AbQBpAGMAcgBvAHMAbwBmAHQALgBjAG8AbQAvAEQAUgBNAC8AMgAwADAANwAvADAAMwAvAFAAbABhAHkAUgBlAGEAZAB5AEgAZQBhAGQAZQByACIAIAB2AGUAcgBzAGkAbwBuAD0AIgA0AC4AMAAuADAALgAwACIAPgA8AEQAQQBUAEEAPgA8AFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBFAFkATABFAE4APgAxADYAPAAvAEsARQBZAEwARQBOAD4APABBAEwARwBJAEQAPgBBAEUAUwBDAFQAUgA8AC8AQQBMAEcASQBEAD4APAAvAFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBJAEQAPgBMADkAVwA5AFcAawBwAFYASwBrACsANAAwAEcASAAzAFkAVQBKAFIAVgBRAD0APQA8AC8ASwBJAEQAPgA8AEMASABFAEMASwBTAFUATQA+AEkASwB6AFkAMgBIAFoATABBAGwASQA9ADwALwBDAEgARQBDAEsAUwBVAE0APgA8AC8ARABBAFQAQQA+ADwALwBXAFIATQBIAEUAQQBEAEUAUgA+AA==
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 | http://example.com/content/sintel/subtitles/subtitles_en.vtt
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/mpd/fixtures/live_profile_dynamic.mpd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | AAAAYXBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAAEEIARIQWr3VL1VKTyq40GH3YUJRVRoIY2FzdGxhYnMiGFdyM1ZMMVZLVHlxNDBHSDNZVUpSVlE9PTIHZGVmYXVsdA==
8 |
9 |
10 | BgIAAAEAAQD8ATwAVwBSAE0ASABFAEEARABFAFIAIAB4AG0AbABuAHMAPQAiAGgAdAB0AHAAOgAvAC8AcwBjAGgAZQBtAGEAcwAuAG0AaQBjAHIAbwBzAG8AZgB0AC4AYwBvAG0ALwBEAFIATQAvADIAMAAwADcALwAwADMALwBQAGwAYQB5AFIAZQBhAGQAeQBIAGUAYQBkAGUAcgAiACAAdgBlAHIAcwBpAG8AbgA9ACIANAAuADAALgAwAC4AMAAiAD4APABEAEEAVABBAD4APABQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsARQBZAEwARQBOAD4AMQA2ADwALwBLAEUAWQBMAEUATgA+ADwAQQBMAEcASQBEAD4AQQBFAFMAQwBUAFIAPAAvAEEATABHAEkARAA+ADwALwBQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsASQBEAD4ATAA5AFcAOQBXAGsAcABWAEsAawArADQAMABHAEgAMwBZAFUASgBSAFYAUQA9AD0APAAvAEsASQBEAD4APABDAEgARQBDAEsAUwBVAE0APgBJAEsAegBZADIASABaAEwAQQBsAEkAPQA8AC8AQwBIAEUAQwBLAFMAVQBNAD4APAAvAEQAQQBUAEEAPgA8AC8AVwBSAE0ASABFAEEARABFAFIAPgA=
11 | AAACJnBzc2gAAAAAmgTweZhAQoarkuZb4IhflQAAAgYGAgAAAQABAPwBPABXAFIATQBIAEUAQQBEAEUAUgAgAHgAbQBsAG4AcwA9ACIAaAB0AHQAcAA6AC8ALwBzAGMAaABlAG0AYQBzAC4AbQBpAGMAcgBvAHMAbwBmAHQALgBjAG8AbQAvAEQAUgBNAC8AMgAwADAANwAvADAAMwAvAFAAbABhAHkAUgBlAGEAZAB5AEgAZQBhAGQAZQByACIAIAB2AGUAcgBzAGkAbwBuAD0AIgA0AC4AMAAuADAALgAwACIAPgA8AEQAQQBUAEEAPgA8AFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBFAFkATABFAE4APgAxADYAPAAvAEsARQBZAEwARQBOAD4APABBAEwARwBJAEQAPgBBAEUAUwBDAFQAUgA8AC8AQQBMAEcASQBEAD4APAAvAFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBJAEQAPgBMADkAVwA5AFcAawBwAFYASwBrACsANAAwAEcASAAzAFkAVQBKAFIAVgBRAD0APQA8AC8ASwBJAEQAPgA8AEMASABFAEMASwBTAFUATQA+AEkASwB6AFkAMgBIAFoATABBAGwASQA9ADwALwBDAEgARQBDAEsAUwBVAE0APgA8AC8ARABBAFQAQQA+ADwALwBXAFIATQBIAEUAQQBEAEUAUgA+AA==
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | AAAAYXBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAAEEIARIQWr3VL1VKTyq40GH3YUJRVRoIY2FzdGxhYnMiGFdyM1ZMMVZLVHlxNDBHSDNZVUpSVlE9PTIHZGVmYXVsdA==
22 |
23 |
24 | BgIAAAEAAQD8ATwAVwBSAE0ASABFAEEARABFAFIAIAB4AG0AbABuAHMAPQAiAGgAdAB0AHAAOgAvAC8AcwBjAGgAZQBtAGEAcwAuAG0AaQBjAHIAbwBzAG8AZgB0AC4AYwBvAG0ALwBEAFIATQAvADIAMAAwADcALwAwADMALwBQAGwAYQB5AFIAZQBhAGQAeQBIAGUAYQBkAGUAcgAiACAAdgBlAHIAcwBpAG8AbgA9ACIANAAuADAALgAwAC4AMAAiAD4APABEAEEAVABBAD4APABQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsARQBZAEwARQBOAD4AMQA2ADwALwBLAEUAWQBMAEUATgA+ADwAQQBMAEcASQBEAD4AQQBFAFMAQwBUAFIAPAAvAEEATABHAEkARAA+ADwALwBQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsASQBEAD4ATAA5AFcAOQBXAGsAcABWAEsAawArADQAMABHAEgAMwBZAFUASgBSAFYAUQA9AD0APAAvAEsASQBEAD4APABDAEgARQBDAEsAUwBVAE0APgBJAEsAegBZADIASABaAEwAQQBsAEkAPQA8AC8AQwBIAEUAQwBLAFMAVQBNAD4APAAvAEQAQQBUAEEAPgA8AC8AVwBSAE0ASABFAEEARABFAFIAPgA=
25 | AAACJnBzc2gAAAAAmgTweZhAQoarkuZb4IhflQAAAgYGAgAAAQABAPwBPABXAFIATQBIAEUAQQBEAEUAUgAgAHgAbQBsAG4AcwA9ACIAaAB0AHQAcAA6AC8ALwBzAGMAaABlAG0AYQBzAC4AbQBpAGMAcgBvAHMAbwBmAHQALgBjAG8AbQAvAEQAUgBNAC8AMgAwADAANwAvADAAMwAvAFAAbABhAHkAUgBlAGEAZAB5AEgAZQBhAGQAZQByACIAIAB2AGUAcgBzAGkAbwBuAD0AIgA0AC4AMAAuADAALgAwACIAPgA8AEQAQQBUAEEAPgA8AFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBFAFkATABFAE4APgAxADYAPAAvAEsARQBZAEwARQBOAD4APABBAEwARwBJAEQAPgBBAEUAUwBDAFQAUgA8AC8AQQBMAEcASQBEAD4APAAvAFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBJAEQAPgBMADkAVwA5AFcAawBwAFYASwBrACsANAAwAEcASAAzAFkAVQBKAFIAVgBRAD0APQA8AC8ASwBJAEQAPgA8AEMASABFAEMASwBTAFUATQA+AEkASwB6AFkAMgBIAFoATABBAGwASQA9ADwALwBDAEgARQBDAEsAUwBVAE0APgA8AC8ARABBAFQAQQA+ADwALwBXAFIATQBIAEUAQQBEAEUAUgA+AA==
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 | http://example.com/content/sintel/subtitles/subtitles_en.vtt
37 |
38 |
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/mpd/fixtures/live_profile_multi_base_url.mpd:
--------------------------------------------------------------------------------
1 |
2 |
3 | ./
4 | ../a/
5 | ../b/
6 |
7 |
8 |
9 |
10 | AAAAYXBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAAEEIARIQWr3VL1VKTyq40GH3YUJRVRoIY2FzdGxhYnMiGFdyM1ZMMVZLVHlxNDBHSDNZVUpSVlE9PTIHZGVmYXVsdA==
11 |
12 |
13 | BgIAAAEAAQD8ATwAVwBSAE0ASABFAEEARABFAFIAIAB4AG0AbABuAHMAPQAiAGgAdAB0AHAAOgAvAC8AcwBjAGgAZQBtAGEAcwAuAG0AaQBjAHIAbwBzAG8AZgB0AC4AYwBvAG0ALwBEAFIATQAvADIAMAAwADcALwAwADMALwBQAGwAYQB5AFIAZQBhAGQAeQBIAGUAYQBkAGUAcgAiACAAdgBlAHIAcwBpAG8AbgA9ACIANAAuADAALgAwAC4AMAAiAD4APABEAEEAVABBAD4APABQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsARQBZAEwARQBOAD4AMQA2ADwALwBLAEUAWQBMAEUATgA+ADwAQQBMAEcASQBEAD4AQQBFAFMAQwBUAFIAPAAvAEEATABHAEkARAA+ADwALwBQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsASQBEAD4ATAA5AFcAOQBXAGsAcABWAEsAawArADQAMABHAEgAMwBZAFUASgBSAFYAUQA9AD0APAAvAEsASQBEAD4APABDAEgARQBDAEsAUwBVAE0APgBJAEsAegBZADIASABaAEwAQQBsAEkAPQA8AC8AQwBIAEUAQwBLAFMAVQBNAD4APAAvAEQAQQBUAEEAPgA8AC8AVwBSAE0ASABFAEEARABFAFIAPgA=
14 | AAACJnBzc2gAAAAAmgTweZhAQoarkuZb4IhflQAAAgYGAgAAAQABAPwBPABXAFIATQBIAEUAQQBEAEUAUgAgAHgAbQBsAG4AcwA9ACIAaAB0AHQAcAA6AC8ALwBzAGMAaABlAG0AYQBzAC4AbQBpAGMAcgBvAHMAbwBmAHQALgBjAG8AbQAvAEQAUgBNAC8AMgAwADAANwAvADAAMwAvAFAAbABhAHkAUgBlAGEAZAB5AEgAZQBhAGQAZQByACIAIAB2AGUAcgBzAGkAbwBuAD0AIgA0AC4AMAAuADAALgAwACIAPgA8AEQAQQBUAEEAPgA8AFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBFAFkATABFAE4APgAxADYAPAAvAEsARQBZAEwARQBOAD4APABBAEwARwBJAEQAPgBBAEUAUwBDAFQAUgA8AC8AQQBMAEcASQBEAD4APAAvAFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBJAEQAPgBMADkAVwA5AFcAawBwAFYASwBrACsANAAwAEcASAAzAFkAVQBKAFIAVgBRAD0APQA8AC8ASwBJAEQAPgA8AEMASABFAEMASwBTAFUATQA+AEkASwB6AFkAMgBIAFoATABBAGwASQA9ADwALwBDAEgARQBDAEsAUwBVAE0APgA8AC8ARABBAFQAQQA+ADwALwBXAFIATQBIAEUAQQBEAEUAUgA+AA==
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | AAAAYXBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAAEEIARIQWr3VL1VKTyq40GH3YUJRVRoIY2FzdGxhYnMiGFdyM1ZMMVZLVHlxNDBHSDNZVUpSVlE9PTIHZGVmYXVsdA==
25 |
26 |
27 | BgIAAAEAAQD8ATwAVwBSAE0ASABFAEEARABFAFIAIAB4AG0AbABuAHMAPQAiAGgAdAB0AHAAOgAvAC8AcwBjAGgAZQBtAGEAcwAuAG0AaQBjAHIAbwBzAG8AZgB0AC4AYwBvAG0ALwBEAFIATQAvADIAMAAwADcALwAwADMALwBQAGwAYQB5AFIAZQBhAGQAeQBIAGUAYQBkAGUAcgAiACAAdgBlAHIAcwBpAG8AbgA9ACIANAAuADAALgAwAC4AMAAiAD4APABEAEEAVABBAD4APABQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsARQBZAEwARQBOAD4AMQA2ADwALwBLAEUAWQBMAEUATgA+ADwAQQBMAEcASQBEAD4AQQBFAFMAQwBUAFIAPAAvAEEATABHAEkARAA+ADwALwBQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsASQBEAD4ATAA5AFcAOQBXAGsAcABWAEsAawArADQAMABHAEgAMwBZAFUASgBSAFYAUQA9AD0APAAvAEsASQBEAD4APABDAEgARQBDAEsAUwBVAE0APgBJAEsAegBZADIASABaAEwAQQBsAEkAPQA8AC8AQwBIAEUAQwBLAFMAVQBNAD4APAAvAEQAQQBUAEEAPgA8AC8AVwBSAE0ASABFAEEARABFAFIAPgA=
28 | AAACJnBzc2gAAAAAmgTweZhAQoarkuZb4IhflQAAAgYGAgAAAQABAPwBPABXAFIATQBIAEUAQQBEAEUAUgAgAHgAbQBsAG4AcwA9ACIAaAB0AHQAcAA6AC8ALwBzAGMAaABlAG0AYQBzAC4AbQBpAGMAcgBvAHMAbwBmAHQALgBjAG8AbQAvAEQAUgBNAC8AMgAwADAANwAvADAAMwAvAFAAbABhAHkAUgBlAGEAZAB5AEgAZQBhAGQAZQByACIAIAB2AGUAcgBzAGkAbwBuAD0AIgA0AC4AMAAuADAALgAwACIAPgA8AEQAQQBUAEEAPgA8AFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBFAFkATABFAE4APgAxADYAPAAvAEsARQBZAEwARQBOAD4APABBAEwARwBJAEQAPgBBAEUAUwBDAFQAUgA8AC8AQQBMAEcASQBEAD4APAAvAFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBJAEQAPgBMADkAVwA5AFcAawBwAFYASwBrACsANAAwAEcASAAzAFkAVQBKAFIAVgBRAD0APQA8AC8ASwBJAEQAPgA8AEMASABFAEMASwBTAFUATQA+AEkASwB6AFkAMgBIAFoATABBAGwASQA9ADwALwBDAEgARQBDAEsAUwBVAE0APgA8AC8ARABBAFQAQQA+ADwALwBXAFIATQBIAEUAQQBEAEUAUgA+AA==
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | http://example.com/content/sintel/subtitles/subtitles_en.vtt
40 |
41 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/mpd/fixtures/location.mpd:
--------------------------------------------------------------------------------
1 |
2 |
3 | https://example.com/location.mpd
4 |
5 |
--------------------------------------------------------------------------------
/mpd/fixtures/multiple_supplementals.mpd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | http://localhost:8002/dash/
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/mpd/fixtures/newperiod.mpd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/mpd/fixtures/ondemand_profile.mpd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | AAAAYXBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAAEEIARIQWr3VL1VKTyq40GH3YUJRVRoIY2FzdGxhYnMiGFdyM1ZMMVZLVHlxNDBHSDNZVUpSVlE9PTIHZGVmYXVsdA==
8 |
9 |
10 | BgIAAAEAAQD8ATwAVwBSAE0ASABFAEEARABFAFIAIAB4AG0AbABuAHMAPQAiAGgAdAB0AHAAOgAvAC8AcwBjAGgAZQBtAGEAcwAuAG0AaQBjAHIAbwBzAG8AZgB0AC4AYwBvAG0ALwBEAFIATQAvADIAMAAwADcALwAwADMALwBQAGwAYQB5AFIAZQBhAGQAeQBIAGUAYQBkAGUAcgAiACAAdgBlAHIAcwBpAG8AbgA9ACIANAAuADAALgAwAC4AMAAiAD4APABEAEEAVABBAD4APABQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsARQBZAEwARQBOAD4AMQA2ADwALwBLAEUAWQBMAEUATgA+ADwAQQBMAEcASQBEAD4AQQBFAFMAQwBUAFIAPAAvAEEATABHAEkARAA+ADwALwBQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsASQBEAD4ATAA5AFcAOQBXAGsAcABWAEsAawArADQAMABHAEgAMwBZAFUASgBSAFYAUQA9AD0APAAvAEsASQBEAD4APABDAEgARQBDAEsAUwBVAE0APgBJAEsAegBZADIASABaAEwAQQBsAEkAPQA8AC8AQwBIAEUAQwBLAFMAVQBNAD4APAAvAEQAQQBUAEEAPgA8AC8AVwBSAE0ASABFAEEARABFAFIAPgA=
11 | AAACJnBzc2gAAAAAmgTweZhAQoarkuZb4IhflQAAAgYGAgAAAQABAPwBPABXAFIATQBIAEUAQQBEAEUAUgAgAHgAbQBsAG4AcwA9ACIAaAB0AHQAcAA6AC8ALwBzAGMAaABlAG0AYQBzAC4AbQBpAGMAcgBvAHMAbwBmAHQALgBjAG8AbQAvAEQAUgBNAC8AMgAwADAANwAvADAAMwAvAFAAbABhAHkAUgBlAGEAZAB5AEgAZQBhAGQAZQByACIAIAB2AGUAcgBzAGkAbwBuAD0AIgA0AC4AMAAuADAALgAwACIAPgA8AEQAQQBUAEEAPgA8AFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBFAFkATABFAE4APgAxADYAPAAvAEsARQBZAEwARQBOAD4APABBAEwARwBJAEQAPgBBAEUAUwBDAFQAUgA8AC8AQQBMAEcASQBEAD4APAAvAFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBJAEQAPgBMADkAVwA5AFcAawBwAFYASwBrACsANAAwAEcASAAzAFkAVQBKAFIAVgBRAD0APQA8AC8ASwBJAEQAPgA8AEMASABFAEMASwBTAFUATQA+AEkASwB6AFkAMgBIAFoATABBAGwASQA9ADwALwBDAEgARQBDAEsAUwBVAE0APgA8AC8ARABBAFQAQQA+ADwALwBXAFIATQBIAEUAQQBEAEUAUgA+AA==
12 |
13 |
14 | 800k/output-audio-und.mp4
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | AAAAYXBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAAEEIARIQWr3VL1VKTyq40GH3YUJRVRoIY2FzdGxhYnMiGFdyM1ZMMVZLVHlxNDBHSDNZVUpSVlE9PTIHZGVmYXVsdA==
25 |
26 |
27 | BgIAAAEAAQD8ATwAVwBSAE0ASABFAEEARABFAFIAIAB4AG0AbABuAHMAPQAiAGgAdAB0AHAAOgAvAC8AcwBjAGgAZQBtAGEAcwAuAG0AaQBjAHIAbwBzAG8AZgB0AC4AYwBvAG0ALwBEAFIATQAvADIAMAAwADcALwAwADMALwBQAGwAYQB5AFIAZQBhAGQAeQBIAGUAYQBkAGUAcgAiACAAdgBlAHIAcwBpAG8AbgA9ACIANAAuADAALgAwAC4AMAAiAD4APABEAEEAVABBAD4APABQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsARQBZAEwARQBOAD4AMQA2ADwALwBLAEUAWQBMAEUATgA+ADwAQQBMAEcASQBEAD4AQQBFAFMAQwBUAFIAPAAvAEEATABHAEkARAA+ADwALwBQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsASQBEAD4ATAA5AFcAOQBXAGsAcABWAEsAawArADQAMABHAEgAMwBZAFUASgBSAFYAUQA9AD0APAAvAEsASQBEAD4APABDAEgARQBDAEsAUwBVAE0APgBJAEsAegBZADIASABaAEwAQQBsAEkAPQA8AC8AQwBIAEUAQwBLAFMAVQBNAD4APAAvAEQAQQBUAEEAPgA8AC8AVwBSAE0ASABFAEEARABFAFIAPgA=
28 | AAACJnBzc2gAAAAAmgTweZhAQoarkuZb4IhflQAAAgYGAgAAAQABAPwBPABXAFIATQBIAEUAQQBEAEUAUgAgAHgAbQBsAG4AcwA9ACIAaAB0AHQAcAA6AC8ALwBzAGMAaABlAG0AYQBzAC4AbQBpAGMAcgBvAHMAbwBmAHQALgBjAG8AbQAvAEQAUgBNAC8AMgAwADAANwAvADAAMwAvAFAAbABhAHkAUgBlAGEAZAB5AEgAZQBhAGQAZQByACIAIAB2AGUAcgBzAGkAbwBuAD0AIgA0AC4AMAAuADAALgAwACIAPgA8AEQAQQBUAEEAPgA8AFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBFAFkATABFAE4APgAxADYAPAAvAEsARQBZAEwARQBOAD4APABBAEwARwBJAEQAPgBBAEUAUwBDAFQAUgA8AC8AQQBMAEcASQBEAD4APAAvAFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBJAEQAPgBMADkAVwA5AFcAawBwAFYASwBrACsANAAwAEcASAAzAFkAVQBKAFIAVgBRAD0APQA8AC8ASwBJAEQAPgA8AEMASABFAEMASwBTAFUATQA+AEkASwB6AFkAMgBIAFoATABBAGwASQA9ADwALwBDAEgARQBDAEsAUwBVAE0APgA8AC8ARABBAFQAQQA+ADwALwBXAFIATQBIAEUAQQBEAEUAUgA+AA==
29 |
30 |
31 | 800k/output-video-1.mp4
32 |
33 |
34 |
35 |
36 |
37 | 1200k/output-video-1.mp4
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 | http://example.com/content/sintel/subtitles/subtitles_en.vtt
46 |
47 |
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/mpd/fixtures/scte35.mpd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | /AASAAAAAAAAAAAAAQQAAABifGO7
10 |
11 |
12 |
13 |
14 | /DAgAAAAAAAAAP/wBQb+AABb0AAAABAAAAEAAQCA==
15 |
16 |
17 |
18 |
19 | /AAcAAAAAAAAAAAACwUAAAAAfw8AAAAAAAAA0q3UPw==
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/mpd/fixtures/segment_list.mpd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | http://localhost:8002/dash/
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/mpd/fixtures/segment_timeline.mpd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | http://localhost:8002/public/
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/mpd/fixtures/segment_timeline_multi_period.mpd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
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 |
--------------------------------------------------------------------------------
/mpd/fixtures/truncate.mpd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
--------------------------------------------------------------------------------
/mpd/fixtures/truncate_short.mpd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/mpd/mpd_attr.go:
--------------------------------------------------------------------------------
1 | package mpd
2 |
3 | type AttrMPD interface {
4 | GetStrptr() *string
5 | }
6 |
7 | type attrAvailabilityStartTime struct {
8 | strptr *string
9 | }
10 |
11 | func (attr *attrAvailabilityStartTime) GetStrptr() *string {
12 | return attr.strptr
13 | }
14 |
15 | // AttrAvailabilityStartTime returns AttrMPD object for NewMPD
16 | func AttrAvailabilityStartTime(value string) AttrMPD {
17 | return &attrAvailabilityStartTime{strptr: &value}
18 | }
19 |
20 | type attrMinimumUpdatePeriod struct {
21 | strptr *string
22 | }
23 |
24 | func (attr *attrMinimumUpdatePeriod) GetStrptr() *string {
25 | return attr.strptr
26 | }
27 |
28 | // AttrMinimumUpdatePeriod returns AttrMPD object for NewMPD
29 | func AttrMinimumUpdatePeriod(value string) AttrMPD {
30 | return &attrMinimumUpdatePeriod{strptr: &value}
31 | }
32 |
33 | type attrMediaPresentationDuration struct {
34 | strptr *string
35 | }
36 |
37 | func (attr *attrMediaPresentationDuration) GetStrptr() *string {
38 | return attr.strptr
39 | }
40 |
41 | // AttrMediaPresentationDuration returns AttrMPD object for NewMPD
42 | func AttrMediaPresentationDuration(value string) AttrMPD {
43 | return &attrMediaPresentationDuration{strptr: &value}
44 | }
45 |
46 | type attrPublishTime struct {
47 | strptr *string
48 | }
49 |
50 | func (attr *attrPublishTime) GetStrptr() *string {
51 | return attr.strptr
52 | }
53 |
54 | // AttrPublishTime returns AttrMPD object for NewMPD
55 | func AttrPublishTime(value string) AttrMPD {
56 | return &attrPublishTime{strptr: &value}
57 | }
58 |
--------------------------------------------------------------------------------
/mpd/mpd_read_write.go:
--------------------------------------------------------------------------------
1 | package mpd
2 |
3 | import (
4 | "bufio"
5 | "bytes"
6 | "encoding/xml"
7 | "io"
8 | "os"
9 | )
10 |
11 | // Reads an MPD XML file from disk into a MPD object.
12 | // path - File path to an MPD on disk
13 | func ReadFromFile(path string) (*MPD, error) {
14 | f, err := os.OpenFile(path, os.O_RDONLY, 0666)
15 | if err != nil {
16 | return nil, err
17 | }
18 | defer f.Close()
19 |
20 | return Read(f)
21 | }
22 |
23 | // Reads a string into a MPD object.
24 | // xmlStr - MPD manifest data as a string.
25 | func ReadFromString(xmlStr string) (*MPD, error) {
26 | b := bytes.NewBufferString(xmlStr)
27 | return Read(b)
28 | }
29 |
30 | // Reads from an io.Reader interface into an MPD object.
31 | // r - Must implement the io.Reader interface.
32 | func Read(r io.Reader) (*MPD, error) {
33 | var mpd MPD
34 | d := xml.NewDecoder(r)
35 | err := d.Decode(&mpd)
36 | if err != nil {
37 | return nil, err
38 | }
39 | return &mpd, nil
40 | }
41 |
42 | // Writes an MPD object to a file on disk.
43 | // path - Output path to write the manifest to.
44 | func (m *MPD) WriteToFile(path string) error {
45 | // Open the file to write the XML to
46 | f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
47 | if err != nil {
48 | return err
49 | }
50 | defer f.Close()
51 | if err = m.Write(f); err != nil {
52 | return err
53 | }
54 | if err = f.Sync(); err != nil {
55 | return err
56 | }
57 | return err
58 | }
59 |
60 | // Writes an MPD object to a string.
61 | func (m *MPD) WriteToString() (string, error) {
62 | var b bytes.Buffer
63 | w := bufio.NewWriter(&b)
64 | err := m.Write(w)
65 | if err != nil {
66 | return "", err
67 | }
68 | err = w.Flush()
69 | if err != nil {
70 | return "", err
71 | }
72 | return b.String(), err
73 | }
74 |
75 | // Writes an MPD object to an io.Writer interface
76 | // w - Must implement the io.Writer interface.
77 | func (m *MPD) Write(w io.Writer) error {
78 | b, err := xml.MarshalIndent(m, "", " ")
79 | if err != nil {
80 | return err
81 | }
82 |
83 | _, _ = w.Write([]byte(xml.Header))
84 | _, _ = w.Write(b)
85 | _, _ = w.Write([]byte("\n"))
86 | return nil
87 | }
88 |
--------------------------------------------------------------------------------
/mpd/mpd_read_write_test.go:
--------------------------------------------------------------------------------
1 | package mpd
2 |
3 | import (
4 | "os"
5 | "testing"
6 | "time"
7 |
8 | "github.com/zencoder/go-dash/v3/helpers/ptrs"
9 | "github.com/zencoder/go-dash/v3/helpers/require"
10 | "github.com/zencoder/go-dash/v3/helpers/testfixtures"
11 | )
12 |
13 | func TestReadingManifests(t *testing.T) {
14 | var testCases = []struct {
15 | err, filepath string
16 | }{
17 | {filepath: "fixtures/live_profile.mpd", err: ""},
18 | {filepath: "fixtures/ondemand_profile.mpd", err: ""},
19 | {filepath: "fixtures/invalid.mpd", err: "XML syntax error on line 3: unexpected EOF"},
20 | {filepath: "doesntexist.mpd", err: "open doesntexist.mpd: no such file or directory"},
21 | }
22 |
23 | for _, tc := range testCases {
24 | // Test reading from manifest files
25 | if m, err := ReadFromFile(tc.filepath); tc.err == "" {
26 | require.NoError(t, err, "Error while reading "+tc.filepath)
27 | require.NotNil(t, m, "Empty result from reading "+tc.filepath)
28 | } else {
29 | require.EqualError(t, err, tc.err)
30 | }
31 |
32 | // Test reading valid files from strings
33 | if tc.err == "" {
34 | xmlStr := testfixtures.LoadFixture(tc.filepath)
35 | _, err := ReadFromString(xmlStr)
36 | require.NotNil(t, xmlStr)
37 | require.NoError(t, err)
38 | }
39 | }
40 | }
41 |
42 | func TestNewMPDLiveWriteToString(t *testing.T) {
43 | m := NewMPD(DASH_PROFILE_LIVE, VALID_MEDIA_PRESENTATION_DURATION, VALID_MIN_BUFFER_TIME,
44 | AttrAvailabilityStartTime(VALID_AVAILABILITY_START_TIME))
45 |
46 | xmlStr, err := m.WriteToString()
47 | require.NoError(t, err)
48 | expectedXML := `
49 |
50 |
51 |
52 | `
53 | require.EqualString(t, expectedXML, xmlStr)
54 | }
55 |
56 | func TestNewDynamicMPDLiveWriteToString(t *testing.T) {
57 | m := NewDynamicMPD(DASH_PROFILE_LIVE, VALID_AVAILABILITY_START_TIME, VALID_MIN_BUFFER_TIME,
58 | AttrMediaPresentationDuration(VALID_MEDIA_PRESENTATION_DURATION),
59 | AttrMinimumUpdatePeriod(VALID_MINIMUM_UPDATE_PERIOD))
60 |
61 | xmlStr, err := m.WriteToString()
62 | require.NoError(t, err)
63 | expectedXML := `
64 |
65 |
66 |
67 |
68 | `
69 | require.EqualString(t, expectedXML, xmlStr)
70 | }
71 |
72 | func TestNewDynamicMPDLiveWithPeriodStartWriteToString(t *testing.T) {
73 | m := NewDynamicMPD(DASH_PROFILE_LIVE, VALID_AVAILABILITY_START_TIME, VALID_MIN_BUFFER_TIME,
74 | AttrMediaPresentationDuration(VALID_MEDIA_PRESENTATION_DURATION),
75 | AttrMinimumUpdatePeriod(VALID_MINIMUM_UPDATE_PERIOD))
76 |
77 | // Set first period start time to PT0S
78 | p := m.GetCurrentPeriod()
79 | start := Duration(time.Duration(0))
80 | p.Start = &start
81 |
82 | xmlStr, err := m.WriteToString()
83 | require.NoError(t, err)
84 | expectedXML := `
85 |
86 |
87 |
88 |
89 | `
90 | require.EqualString(t, expectedXML, xmlStr)
91 | }
92 |
93 | func TestNewDynamicMPDLiveWithSuggestedPresentationDelayToString(t *testing.T) {
94 | m := NewDynamicMPD(DASH_PROFILE_LIVE, VALID_AVAILABILITY_START_TIME, VALID_MIN_BUFFER_TIME,
95 | AttrMediaPresentationDuration(VALID_MEDIA_PRESENTATION_DURATION),
96 | AttrMinimumUpdatePeriod(VALID_MINIMUM_UPDATE_PERIOD))
97 |
98 | // Set first period start time to PT0S
99 | spd := Duration(time.Duration(18) * time.Second)
100 | m.SuggestedPresentationDelay = &spd
101 |
102 | xmlStr, err := m.WriteToString()
103 | require.NoError(t, err)
104 | expectedXML := `
105 |
106 |
107 |
108 |
109 | `
110 | require.EqualString(t, expectedXML, xmlStr)
111 | }
112 |
113 | func TestNewMPDOnDemandWriteToString(t *testing.T) {
114 | m := NewMPD(DASH_PROFILE_ONDEMAND, VALID_MEDIA_PRESENTATION_DURATION, VALID_MIN_BUFFER_TIME)
115 |
116 | xmlStr, err := m.WriteToString()
117 | require.NoError(t, err)
118 | expectedXML := `
119 |
120 |
121 |
122 | `
123 | require.EqualString(t, expectedXML, xmlStr)
124 | }
125 |
126 | func TestAddNewAdaptationSetAudioWriteToString(t *testing.T) {
127 | m := NewMPD(DASH_PROFILE_LIVE, VALID_MEDIA_PRESENTATION_DURATION, VALID_MIN_BUFFER_TIME)
128 |
129 | _, _ = m.AddNewAdaptationSetAudioWithID("7357", DASH_MIME_TYPE_AUDIO_MP4, VALID_SEGMENT_ALIGNMENT, VALID_START_WITH_SAP, VALID_LANG)
130 |
131 | xmlStr, err := m.WriteToString()
132 | require.NoError(t, err)
133 | expectedXML := `
134 |
135 |
136 |
137 |
138 |
139 | `
140 | require.EqualString(t, expectedXML, xmlStr)
141 | }
142 |
143 | func TestAddNewAdaptationSetVideoWriteToString(t *testing.T) {
144 | m := NewMPD(DASH_PROFILE_LIVE, VALID_MEDIA_PRESENTATION_DURATION, VALID_MIN_BUFFER_TIME)
145 |
146 | as, err := m.AddNewAdaptationSetVideoWithID("7357", DASH_MIME_TYPE_VIDEO_MP4, VALID_SCAN_TYPE, VALID_SEGMENT_ALIGNMENT, VALID_START_WITH_SAP)
147 | require.NoError(t, err)
148 |
149 | as.MinWidth = ptrs.Strptr("720")
150 | as.MaxWidth = ptrs.Strptr("720")
151 | as.MinHeight = ptrs.Strptr("480")
152 | as.MaxHeight = ptrs.Strptr("480")
153 |
154 | xmlStr, err := m.WriteToString()
155 | require.NoError(t, err)
156 | expectedXML := `
157 |
158 |
159 |
160 |
161 |
162 | `
163 | require.EqualString(t, expectedXML, xmlStr)
164 | }
165 |
166 | func TestAddNewAdaptationSetSubtitleWriteToString(t *testing.T) {
167 | m := NewMPD(DASH_PROFILE_LIVE, VALID_MEDIA_PRESENTATION_DURATION, VALID_MIN_BUFFER_TIME)
168 |
169 | _, _ = m.AddNewAdaptationSetSubtitleWithID("7357", DASH_MIME_TYPE_SUBTITLE_VTT, VALID_LANG, VALID_SUBTITLE_LABEL)
170 |
171 | xmlStr, err := m.WriteToString()
172 | require.NoError(t, err)
173 | expectedXML := `
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 | `
182 | require.EqualString(t, expectedXML, xmlStr)
183 | }
184 |
185 | func TestExampleAddNewPeriod(t *testing.T) {
186 | // a new MPD is created with a single Period
187 | m := NewMPD(DASH_PROFILE_LIVE, VALID_MEDIA_PRESENTATION_DURATION, VALID_MIN_BUFFER_TIME)
188 |
189 | // you can add content to the Period
190 | p := m.GetCurrentPeriod()
191 | as, _ := p.AddNewAdaptationSetVideoWithID("1", DASH_MIME_TYPE_VIDEO_MP4, VALID_SCAN_TYPE, VALID_SEGMENT_ALIGNMENT, VALID_START_WITH_SAP)
192 | _, _ = as.SetNewSegmentTemplate(1968, "$RepresentationID$/video-1.mp4", "$RepresentationID$/video-1/seg-$Number$.m4f", 0, 1000)
193 |
194 | // or directly to the MPD, which will use the current Period.
195 | as, _ = m.AddNewAdaptationSetAudioWithID("1", DASH_MIME_TYPE_AUDIO_MP4, VALID_SEGMENT_ALIGNMENT, VALID_START_WITH_SAP, VALID_LANG)
196 | _, _ = as.SetNewSegmentTemplate(1968, "$RepresentationID$/audio-1.mp4", "$RepresentationID$/audio-1/seg-$Number$.m4f", 0, 1000)
197 |
198 | // add a second period
199 | p = m.AddNewPeriod()
200 | p.SetDuration(3 * time.Minute)
201 | as, _ = p.AddNewAdaptationSetVideoWithID("2", DASH_MIME_TYPE_VIDEO_MP4, VALID_SCAN_TYPE, VALID_SEGMENT_ALIGNMENT, VALID_START_WITH_SAP)
202 | _, _ = as.SetNewSegmentTemplate(1968, "$RepresentationID$/video-2.mp4", "$RepresentationID$/video-2/seg-$Number$.m4f", 0, 1000)
203 |
204 | as, _ = m.AddNewAdaptationSetAudioWithID("2", DASH_MIME_TYPE_AUDIO_MP4, VALID_SEGMENT_ALIGNMENT, VALID_START_WITH_SAP, VALID_LANG)
205 | _, _ = as.SetNewSegmentTemplate(1968, "$RepresentationID$/audio-2.mp4", "$RepresentationID$/audio-2/seg-$Number$.m4f", 0, 1000)
206 |
207 | xmlStr, err := m.WriteToString()
208 | require.NoError(t, err)
209 | testfixtures.CompareFixture(t, "fixtures/newperiod.mpd", xmlStr)
210 | }
211 |
212 | func TestAddNewAccessibilityElementWriteToString(t *testing.T) {
213 | m := NewMPD(DASH_PROFILE_LIVE, VALID_MEDIA_PRESENTATION_DURATION, VALID_MIN_BUFFER_TIME)
214 | audioAS, err := m.AddNewAdaptationSetAudioWithID("7357", DASH_MIME_TYPE_AUDIO_MP4, VALID_SEGMENT_ALIGNMENT,
215 | VALID_START_WITH_SAP, VALID_LANG)
216 | if err != nil {
217 | t.Errorf("AddNewAccessibilityElement() error adding audio adaptation set: %v", err)
218 | return
219 | }
220 |
221 | _, err = audioAS.AddNewAccessibilityElement(ACCESSIBILITY_ELEMENT_SCHEME_DESCRIPTIVE_AUDIO, "1")
222 | if err != nil {
223 | t.Errorf("AddNewAccessibilityElement() error adding accessibility element: %v", err)
224 | return
225 | }
226 |
227 | xmlStr, err := m.WriteToString()
228 | require.NoError(t, err)
229 | expectedXML := `
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 | `
238 | require.EqualString(t, expectedXML, xmlStr)
239 | }
240 |
241 | func LiveProfile() *MPD {
242 | m := NewMPD(DASH_PROFILE_LIVE, VALID_MEDIA_PRESENTATION_DURATION, VALID_MIN_BUFFER_TIME,
243 | AttrAvailabilityStartTime(VALID_AVAILABILITY_START_TIME))
244 |
245 | audioAS, _ := m.AddNewAdaptationSetAudioWithID("7357", DASH_MIME_TYPE_AUDIO_MP4, VALID_SEGMENT_ALIGNMENT, VALID_START_WITH_SAP, VALID_LANG)
246 |
247 | _, _ = audioAS.AddNewContentProtectionRoot("08e367028f33436ca5dd60ffe5571e60")
248 | _, _ = audioAS.AddNewContentProtectionSchemeWidevineWithPSSH(getValidWVHeaderBytes())
249 | _, _ = audioAS.AddNewContentProtectionSchemePlayreadyWithPSSH(VALID_PLAYREADY_PRO)
250 |
251 | _, _ = audioAS.AddNewRole("urn:mpeg:dash:role:2011", VALID_ROLE)
252 |
253 | _, _ = audioAS.SetNewSegmentTemplate(1968, "$RepresentationID$/audio/en/init.mp4", "$RepresentationID$/audio/en/seg-$Number$.m4f", 0, 1000)
254 | _, _ = audioAS.AddNewRepresentationAudio(44100, 67095, "mp4a.40.2", "800")
255 | _, _ = audioAS.AddNewAccessibilityElement(ACCESSIBILITY_ELEMENT_SCHEME_DESCRIPTIVE_AUDIO, "1")
256 |
257 | videoAS, _ := m.AddNewAdaptationSetVideoWithID("7357", DASH_MIME_TYPE_VIDEO_MP4, VALID_SCAN_TYPE, VALID_SEGMENT_ALIGNMENT, VALID_START_WITH_SAP)
258 |
259 | _, _ = videoAS.AddNewContentProtectionRoot("08e367028f33436ca5dd60ffe5571e60")
260 | _, _ = videoAS.AddNewContentProtectionSchemeWidevineWithPSSH(getValidWVHeaderBytes())
261 | _, _ = videoAS.AddNewContentProtectionSchemePlayreadyWithPSSH(VALID_PLAYREADY_PRO)
262 |
263 | _, _ = videoAS.AddNewRole("urn:mpeg:dash:role:2011", VALID_ROLE)
264 |
265 | _, _ = videoAS.SetNewSegmentTemplate(1968, "$RepresentationID$/video/1/init.mp4", "$RepresentationID$/video/1/seg-$Number$.m4f", 0, 1000)
266 | _, _ = videoAS.AddNewRepresentationVideo(1518664, "avc1.4d401f", "800", "30000/1001", 960, 540)
267 | _, _ = videoAS.AddNewRepresentationVideo(1911775, "avc1.4d401f", "1000", "30000/1001", 1024, 576)
268 | _, _ = videoAS.AddNewRepresentationVideo(2295158, "avc1.4d401f", "1200", "30000/1001", 1024, 576)
269 | _, _ = videoAS.AddNewRepresentationVideo(2780732, "avc1.4d401f", "1500", "30000/1001", 1280, 720)
270 |
271 | subtitleAS, _ := m.AddNewAdaptationSetSubtitleWithID("7357", DASH_MIME_TYPE_SUBTITLE_VTT, VALID_LANG, VALID_SUBTITLE_LABEL)
272 | subtitleRep, _ := subtitleAS.AddNewRepresentationSubtitle(VALID_SUBTITLE_BANDWIDTH, VALID_SUBTITLE_ID)
273 | _ = subtitleRep.SetNewBaseURL(VALID_SUBTITLE_URL)
274 |
275 | return m
276 | }
277 |
278 | func TestFullLiveProfileWriteToString(t *testing.T) {
279 | m := LiveProfile()
280 | require.NotNil(t, m)
281 | xmlStr, err := m.WriteToString()
282 | require.NoError(t, err)
283 | testfixtures.CompareFixture(t, "fixtures/live_profile.mpd", xmlStr)
284 | }
285 |
286 | func TestFullLiveProfileMultiBaseURLWriteToString(t *testing.T) {
287 | m := LiveProfile()
288 | require.NotNil(t, m)
289 |
290 | m.BaseURL = []string{"./", "../a/", "../b/"}
291 |
292 | xmlStr, err := m.WriteToString()
293 | require.NoError(t, err)
294 | testfixtures.CompareFixture(t, "fixtures/live_profile_multi_base_url.mpd", xmlStr)
295 | }
296 |
297 | func TestFullLiveProfileWriteToFile(t *testing.T) {
298 | m := LiveProfile()
299 | require.NotNil(t, m)
300 | err := m.WriteToFile("test_live.mpd")
301 | xmlStr := testfixtures.LoadFixture("test_live.mpd")
302 | expectedXML := testfixtures.LoadFixture("fixtures/live_profile.mpd")
303 | require.EqualString(t, expectedXML, xmlStr)
304 | defer os.Remove("test_live.mpd")
305 | require.NoError(t, err)
306 | }
307 |
308 | func LiveProfileDynamic() *MPD {
309 | m := NewDynamicMPD(DASH_PROFILE_LIVE, VALID_AVAILABILITY_START_TIME, VALID_MIN_BUFFER_TIME,
310 | AttrMediaPresentationDuration(VALID_MEDIA_PRESENTATION_DURATION),
311 | AttrMinimumUpdatePeriod(VALID_MINIMUM_UPDATE_PERIOD))
312 |
313 | audioAS, _ := m.AddNewAdaptationSetAudioWithID("7357", DASH_MIME_TYPE_AUDIO_MP4, VALID_SEGMENT_ALIGNMENT, VALID_START_WITH_SAP, VALID_LANG)
314 |
315 | _, _ = audioAS.AddNewContentProtectionRoot("08e367028f33436ca5dd60ffe5571e60")
316 | _, _ = audioAS.AddNewContentProtectionSchemeWidevineWithPSSH(getValidWVHeaderBytes())
317 | _, _ = audioAS.AddNewContentProtectionSchemePlayreadyWithPSSH(VALID_PLAYREADY_PRO)
318 |
319 | _, _ = audioAS.AddNewRole("urn:mpeg:dash:role:2011", VALID_ROLE)
320 |
321 | _, _ = audioAS.SetNewSegmentTemplate(1968, "$RepresentationID$/audio/en/init.mp4", "$RepresentationID$/audio/en/seg-$Number$.m4f", 0, 1000)
322 | _, _ = audioAS.AddNewRepresentationAudio(44100, 67095, "mp4a.40.2", "800")
323 | _, _ = audioAS.AddNewAccessibilityElement(ACCESSIBILITY_ELEMENT_SCHEME_DESCRIPTIVE_AUDIO, "1")
324 |
325 | videoAS, _ := m.AddNewAdaptationSetVideoWithID("7357", DASH_MIME_TYPE_VIDEO_MP4, VALID_SCAN_TYPE, VALID_SEGMENT_ALIGNMENT, VALID_START_WITH_SAP)
326 |
327 | _, _ = videoAS.AddNewContentProtectionRoot("08e367028f33436ca5dd60ffe5571e60")
328 | _, _ = videoAS.AddNewContentProtectionSchemeWidevineWithPSSH(getValidWVHeaderBytes())
329 | _, _ = videoAS.AddNewContentProtectionSchemePlayreadyWithPSSH(VALID_PLAYREADY_PRO)
330 |
331 | _, _ = videoAS.AddNewRole("urn:mpeg:dash:role:2011", VALID_ROLE)
332 |
333 | _, _ = videoAS.SetNewSegmentTemplate(1968, "$RepresentationID$/video/1/init.mp4", "$RepresentationID$/video/1/seg-$Number$.m4f", 0, 1000)
334 | _, _ = videoAS.AddNewRepresentationVideo(1518664, "avc1.4d401f", "800", "30000/1001", 960, 540)
335 | _, _ = videoAS.AddNewRepresentationVideo(1911775, "avc1.4d401f", "1000", "30000/1001", 1024, 576)
336 | _, _ = videoAS.AddNewRepresentationVideo(2295158, "avc1.4d401f", "1200", "30000/1001", 1024, 576)
337 | _, _ = videoAS.AddNewRepresentationVideo(2780732, "avc1.4d401f", "1500", "30000/1001", 1280, 720)
338 |
339 | subtitleAS, _ := m.AddNewAdaptationSetSubtitleWithID("7357", DASH_MIME_TYPE_SUBTITLE_VTT, VALID_LANG, VALID_SUBTITLE_LABEL)
340 | subtitleRep, _ := subtitleAS.AddNewRepresentationSubtitle(VALID_SUBTITLE_BANDWIDTH, VALID_SUBTITLE_ID)
341 | _ = subtitleRep.SetNewBaseURL(VALID_SUBTITLE_URL)
342 |
343 | return m
344 | }
345 |
346 | func TestFullLiveProfileDynamicWriteToString(t *testing.T) {
347 | m := LiveProfileDynamic()
348 | require.NotNil(t, m)
349 | xmlStr, err := m.WriteToString()
350 | require.NoError(t, err)
351 | testfixtures.CompareFixture(t, "fixtures/live_profile_dynamic.mpd", xmlStr)
352 | }
353 |
354 | func TestFullLiveProfileDynamicWriteToFile(t *testing.T) {
355 | m := LiveProfileDynamic()
356 | require.NotNil(t, m)
357 | err := m.WriteToFile("test_live_dynamic.mpd")
358 | xmlStr := testfixtures.LoadFixture("test_live_dynamic.mpd")
359 | expectedXML := testfixtures.LoadFixture("fixtures/live_profile_dynamic.mpd")
360 | require.EqualString(t, expectedXML, xmlStr)
361 | defer os.Remove("test_live_dynamic.mpd")
362 | require.NoError(t, err)
363 | }
364 |
365 | func HbbTVProfile() *MPD {
366 | m := NewMPD(DASH_PROFILE_HBBTV_1_5_LIVE, VALID_MEDIA_PRESENTATION_DURATION, VALID_MIN_BUFFER_TIME)
367 |
368 | audioAS, _ := m.AddNewAdaptationSetAudioWithID("7357", DASH_MIME_TYPE_AUDIO_MP4, VALID_SEGMENT_ALIGNMENT, VALID_START_WITH_SAP, VALID_LANG)
369 |
370 | _, _ = audioAS.AddNewContentProtectionRoot("08e367028f33436ca5dd60ffe5571e60")
371 | _, _ = audioAS.AddNewContentProtectionSchemeWidevineWithPSSH(getValidWVHeaderBytes())
372 | _, _ = audioAS.AddNewContentProtectionSchemePlayreadyWithPSSH(VALID_PLAYREADY_PRO)
373 |
374 | _, _ = audioAS.AddNewRole("urn:mpeg:dash:role:2011", VALID_ROLE)
375 |
376 | _, _ = audioAS.SetNewSegmentTemplate(1968, "$RepresentationID$/audio/en/init.mp4", "$RepresentationID$/audio/en/seg-$Number$.m4f", 0, 1000)
377 | r, _ := audioAS.AddNewRepresentationAudio(44100, 67095, "mp4a.40.2", "800")
378 | _, _ = r.AddNewAudioChannelConfiguration(AUDIO_CHANNEL_CONFIGURATION_MPEG_DASH, "2")
379 | _, _ = audioAS.AddNewAccessibilityElement(ACCESSIBILITY_ELEMENT_SCHEME_DESCRIPTIVE_AUDIO, "1")
380 |
381 | videoAS, _ := m.AddNewAdaptationSetVideoWithID("7357", DASH_MIME_TYPE_VIDEO_MP4, VALID_SCAN_TYPE, VALID_SEGMENT_ALIGNMENT, VALID_START_WITH_SAP)
382 |
383 | _, _ = videoAS.AddNewContentProtectionRoot("08e367028f33436ca5dd60ffe5571e60")
384 | _, _ = videoAS.AddNewContentProtectionSchemeWidevineWithPSSH(getValidWVHeaderBytes())
385 | _, _ = videoAS.AddNewContentProtectionSchemePlayreadyWithPSSH(VALID_PLAYREADY_PRO)
386 |
387 | _, _ = videoAS.AddNewRole("urn:mpeg:dash:role:2011", VALID_ROLE)
388 |
389 | _, _ = videoAS.SetNewSegmentTemplate(1968, "$RepresentationID$/video/1/init.mp4", "$RepresentationID$/video/1/seg-$Number$.m4f", 0, 1000)
390 | _, _ = videoAS.AddNewRepresentationVideo(1518664, "avc1.4d401f", "800", "30000/1001", 960, 540)
391 | _, _ = videoAS.AddNewRepresentationVideo(1911775, "avc1.4d401f", "1000", "30000/1001", 1024, 576)
392 | _, _ = videoAS.AddNewRepresentationVideo(2295158, "avc1.4d401f", "1200", "30000/1001", 1024, 576)
393 | _, _ = videoAS.AddNewRepresentationVideo(2780732, "avc1.4d401f", "1500", "30000/1001", 1280, 720)
394 |
395 | subtitleAS, _ := m.AddNewAdaptationSetSubtitleWithID("7357", DASH_MIME_TYPE_SUBTITLE_VTT, VALID_LANG, VALID_SUBTITLE_LABEL)
396 | subtitleRep, _ := subtitleAS.AddNewRepresentationSubtitle(VALID_SUBTITLE_BANDWIDTH, VALID_SUBTITLE_ID)
397 | _ = subtitleRep.SetNewBaseURL(VALID_SUBTITLE_URL)
398 |
399 | return m
400 | }
401 |
402 | func TestFullHbbTVProfileWriteToString(t *testing.T) {
403 | m := HbbTVProfile()
404 | require.NotNil(t, m)
405 | xmlStr, err := m.WriteToString()
406 | require.NoError(t, err)
407 | testfixtures.CompareFixture(t, "fixtures/hbbtv_profile.mpd", xmlStr)
408 | }
409 |
410 | func TestFullHbbTVProfileWriteToFile(t *testing.T) {
411 | m := HbbTVProfile()
412 | require.NotNil(t, m)
413 | err := m.WriteToFile("test_hbbtv.mpd")
414 | xmlStr := testfixtures.LoadFixture("test_hbbtv.mpd")
415 | testfixtures.CompareFixture(t, "fixtures/hbbtv_profile.mpd", xmlStr)
416 | defer os.Remove("test_hbbtv.mpd")
417 | require.NoError(t, err)
418 | }
419 |
420 | func OnDemandProfile() *MPD {
421 | m := NewMPD(DASH_PROFILE_ONDEMAND, "PT30S", VALID_MIN_BUFFER_TIME)
422 |
423 | audioAS, _ := m.AddNewAdaptationSetAudioWithID("7357", DASH_MIME_TYPE_AUDIO_MP4, VALID_SEGMENT_ALIGNMENT, VALID_START_WITH_SAP, "und")
424 |
425 | _, _ = audioAS.AddNewContentProtectionRoot("08e367028f33436ca5dd60ffe5571e60")
426 | _, _ = audioAS.AddNewContentProtectionSchemeWidevineWithPSSH(getValidWVHeaderBytes())
427 | _, _ = audioAS.AddNewContentProtectionSchemePlayreadyWithPSSH(VALID_PLAYREADY_PRO)
428 | _, _ = audioAS.AddNewAccessibilityElement(ACCESSIBILITY_ELEMENT_SCHEME_DESCRIPTIVE_AUDIO, "1")
429 |
430 | audioRep, _ := audioAS.AddNewRepresentationAudio(44100, 128558, "mp4a.40.5", "800k/audio-und")
431 | _ = audioRep.SetNewBaseURL("800k/output-audio-und.mp4")
432 | _, _ = audioRep.AddNewSegmentBase("629-756", "0-628")
433 |
434 | videoAS, _ := m.AddNewAdaptationSetVideoWithID("7357", DASH_MIME_TYPE_VIDEO_MP4, VALID_SCAN_TYPE, VALID_SEGMENT_ALIGNMENT, VALID_START_WITH_SAP)
435 |
436 | _, _ = videoAS.AddNewContentProtectionRoot("08e367028f33436ca5dd60ffe5571e60")
437 | _, _ = videoAS.AddNewContentProtectionSchemeWidevineWithPSSH(getValidWVHeaderBytes())
438 | _, _ = videoAS.AddNewContentProtectionSchemePlayreadyWithPSSH(VALID_PLAYREADY_PRO)
439 |
440 | videoRep1, _ := videoAS.AddNewRepresentationVideo(1100690, "avc1.4d401e", "800k/video-1", "30000/1001", 640, 360)
441 | _ = videoRep1.SetNewBaseURL("800k/output-video-1.mp4")
442 | _, _ = videoRep1.AddNewSegmentBase("686-813", "0-685")
443 |
444 | videoRep2, _ := videoAS.AddNewRepresentationVideo(1633516, "avc1.4d401f", "1200k/video-1", "30000/1001", 960, 540)
445 | _ = videoRep2.SetNewBaseURL("1200k/output-video-1.mp4")
446 | _, _ = videoRep2.AddNewSegmentBase("686-813", "0-685")
447 |
448 | subtitleAS, _ := m.AddNewAdaptationSetSubtitleWithID("7357", DASH_MIME_TYPE_SUBTITLE_VTT, VALID_LANG, VALID_SUBTITLE_LABEL)
449 | subtitleRep, _ := subtitleAS.AddNewRepresentationSubtitle(VALID_SUBTITLE_BANDWIDTH, VALID_SUBTITLE_ID)
450 | _ = subtitleRep.SetNewBaseURL(VALID_SUBTITLE_URL)
451 |
452 | return m
453 | }
454 |
455 | func TestFullOnDemandProfileWriteToString(t *testing.T) {
456 | m := OnDemandProfile()
457 | require.NotNil(t, m)
458 | xmlStr, err := m.WriteToString()
459 | require.NoError(t, err)
460 | testfixtures.CompareFixture(t, "fixtures/ondemand_profile.mpd", xmlStr)
461 | }
462 |
463 | func TestFullOnDemandProfileWriteToFile(t *testing.T) {
464 | m := OnDemandProfile()
465 | require.NotNil(t, m)
466 | err := m.WriteToFile("test-ondemand.mpd")
467 | xmlStr := testfixtures.LoadFixture("test-ondemand.mpd")
468 | testfixtures.CompareFixture(t, "fixtures/ondemand_profile.mpd", xmlStr)
469 | defer os.Remove("test-ondemand.mpd")
470 | require.NoError(t, err)
471 | }
472 |
473 | func TestWriteToFileInvalidFilePath(t *testing.T) {
474 | m := LiveProfile()
475 | require.NotNil(t, m)
476 | err := m.WriteToFile("")
477 | require.NotNil(t, err)
478 | }
479 |
480 | func TestWriteToFileTruncate(t *testing.T) {
481 | out := "test-truncate.mpd"
482 |
483 | m, err := ReadFromFile("fixtures/truncate.mpd")
484 | require.NoError(t, err)
485 |
486 | err = m.WriteToFile(out)
487 | require.NoError(t, err)
488 |
489 | defer os.Remove(out)
490 |
491 | xmlStr := testfixtures.LoadFixture(out)
492 | testfixtures.CompareFixture(t, "fixtures/truncate.mpd", xmlStr)
493 |
494 | m, err = ReadFromFile("fixtures/truncate_short.mpd")
495 | require.NoError(t, err)
496 |
497 | err = m.WriteToFile(out)
498 | require.NoError(t, err)
499 |
500 | xmlStr = testfixtures.LoadFixture(out)
501 | testfixtures.CompareFixture(t, "fixtures/truncate_short.mpd", xmlStr)
502 | }
503 |
--------------------------------------------------------------------------------
/mpd/mpd_test.go:
--------------------------------------------------------------------------------
1 | package mpd
2 |
3 | import (
4 | "encoding/base64"
5 | "path/filepath"
6 | "strconv"
7 | "testing"
8 |
9 | . "github.com/zencoder/go-dash/v3/helpers/ptrs"
10 | "github.com/zencoder/go-dash/v3/helpers/require"
11 | "github.com/zencoder/go-dash/v3/helpers/testfixtures"
12 | )
13 |
14 | const (
15 | VALID_MEDIA_PRESENTATION_DURATION string = "PT6M16S"
16 | VALID_MIN_BUFFER_TIME string = "PT1.97S"
17 | VALID_AVAILABILITY_START_TIME string = "1970-01-01T00:00:00Z"
18 | VALID_PUBLISH_TIME string = "2020-03-12T10:39:45Z"
19 | VALID_MINIMUM_UPDATE_PERIOD string = "PT5S"
20 | VALID_SCAN_TYPE string = "progressive"
21 | VALID_SEGMENT_ALIGNMENT bool = true
22 | VALID_START_WITH_SAP int64 = 1
23 | VALID_LANG string = "en"
24 | VALID_DURATION int64 = 1968
25 | VALID_INIT_PATH_AUDIO string = "$RepresentationID$/audio/en/init.mp4"
26 | VALID_MEDIA_PATH_AUDIO string = "$RepresentationID$/audio/en/seg-$Number$.m4f"
27 | VALID_START_NUMBER int64 = 0
28 | VALID_TIMESCALE int64 = 1000
29 | VALID_AUDIO_SAMPLE_RATE int64 = 44100
30 | VALID_AUDIO_BITRATE int64 = 67095
31 | VALID_AUDIO_CODEC string = "mp4a.40.2"
32 | VALID_AUDIO_ID string = "800"
33 | VALID_VIDEO_BITRATE int64 = 1518664
34 | VALID_VIDEO_CODEC string = "avc1.4d401f"
35 | VALID_VIDEO_ID string = "800"
36 | VALID_VIDEO_FRAMERATE string = "30000/1001"
37 | VALID_VIDEO_WIDTH int64 = 960
38 | VALID_VIDEO_HEIGHT int64 = 540
39 | VALID_BASE_URL_VIDEO string = "800k/output-video-1.mp4"
40 | VALID_INDEX_RANGE string = "629-756"
41 | VALID_INIT_RANGE string = "0-628"
42 | VALID_PLAYREADY_PRO string = "BgIAAAEAAQD8ATwAVwBSAE0ASABFAEEARABFAFIAIAB4AG0AbABuAHMAPQAiAGgAdAB0AHAAOgAvAC8AcwBjAGgAZQBtAGEAcwAuAG0AaQBjAHIAbwBzAG8AZgB0AC4AYwBvAG0ALwBEAFIATQAvADIAMAAwADcALwAwADMALwBQAGwAYQB5AFIAZQBhAGQAeQBIAGUAYQBkAGUAcgAiACAAdgBlAHIAcwBpAG8AbgA9ACIANAAuADAALgAwAC4AMAAiAD4APABEAEEAVABBAD4APABQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsARQBZAEwARQBOAD4AMQA2ADwALwBLAEUAWQBMAEUATgA+ADwAQQBMAEcASQBEAD4AQQBFAFMAQwBUAFIAPAAvAEEATABHAEkARAA+ADwALwBQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsASQBEAD4ATAA5AFcAOQBXAGsAcABWAEsAawArADQAMABHAEgAMwBZAFUASgBSAFYAUQA9AD0APAAvAEsASQBEAD4APABDAEgARQBDAEsAUwBVAE0APgBJAEsAegBZADIASABaAEwAQQBsAEkAPQA8AC8AQwBIAEUAQwBLAFMAVQBNAD4APAAvAEQAQQBUAEEAPgA8AC8AVwBSAE0ASABFAEEARABFAFIAPgA="
43 | VALID_WV_HEADER string = "CAESEFq91S9VSk8quNBh92FCUVUaCGNhc3RsYWJzIhhXcjNWTDFWS1R5cTQwR0gzWVVKUlZRPT0yB2RlZmF1bHQ="
44 | VALID_SUBTITLE_BANDWIDTH int64 = 256
45 | VALID_SUBTITLE_ID string = "subtitle_en"
46 | VALID_SUBTITLE_LABEL string = "Subtitle (En)"
47 | VALID_SUBTITLE_URL string = "http://example.com/content/sintel/subtitles/subtitles_en.vtt"
48 | VALID_ROLE string = "main"
49 | VALID_LOCATION string = "https://example.com/location.mpd"
50 | VALID_SCHEME_ID_URI string = "https://aomedia.org/emsg/ID3"
51 | )
52 |
53 | func TestNewMPDLive(t *testing.T) {
54 | m := NewMPD(DASH_PROFILE_LIVE, VALID_MEDIA_PRESENTATION_DURATION, VALID_MIN_BUFFER_TIME,
55 | AttrAvailabilityStartTime(VALID_AVAILABILITY_START_TIME))
56 | require.NotNil(t, m)
57 | expectedMPD := &MPD{
58 | XMLNs: Strptr("urn:mpeg:dash:schema:mpd:2011"),
59 | Profiles: Strptr((string)(DASH_PROFILE_LIVE)),
60 | Type: Strptr("static"),
61 | MediaPresentationDuration: Strptr(VALID_MEDIA_PRESENTATION_DURATION),
62 | MinBufferTime: Strptr(VALID_MIN_BUFFER_TIME),
63 | AvailabilityStartTime: Strptr(VALID_AVAILABILITY_START_TIME),
64 | period: &Period{},
65 | Periods: []*Period{{}},
66 | }
67 |
68 | expectedString, err := expectedMPD.WriteToString()
69 | require.NoError(t, err)
70 | actualString, err := m.WriteToString()
71 | require.NoError(t, err)
72 |
73 | require.EqualString(t, expectedString, actualString)
74 | }
75 |
76 | func TestNewDynamicMPDLive(t *testing.T) {
77 | m := NewDynamicMPD(DASH_PROFILE_LIVE, VALID_AVAILABILITY_START_TIME, VALID_MIN_BUFFER_TIME,
78 | AttrMediaPresentationDuration(VALID_MEDIA_PRESENTATION_DURATION),
79 | AttrMinimumUpdatePeriod(VALID_MINIMUM_UPDATE_PERIOD),
80 | AttrPublishTime(VALID_PUBLISH_TIME))
81 | require.NotNil(t, m)
82 | expectedMPD := &MPD{
83 | XMLNs: Strptr("urn:mpeg:dash:schema:mpd:2011"),
84 | Profiles: Strptr((string)(DASH_PROFILE_LIVE)),
85 | Type: Strptr("dynamic"),
86 | MediaPresentationDuration: Strptr(VALID_MEDIA_PRESENTATION_DURATION),
87 | MinBufferTime: Strptr(VALID_MIN_BUFFER_TIME),
88 | AvailabilityStartTime: Strptr(VALID_AVAILABILITY_START_TIME),
89 | MinimumUpdatePeriod: Strptr(VALID_MINIMUM_UPDATE_PERIOD),
90 | period: &Period{},
91 | Periods: []*Period{{}},
92 | UTCTiming: &DescriptorType{},
93 | PublishTime: Strptr(VALID_PUBLISH_TIME),
94 | }
95 |
96 | expectedString, err := expectedMPD.WriteToString()
97 | require.NoError(t, err)
98 | actualString, err := m.WriteToString()
99 | require.NoError(t, err)
100 |
101 | require.EqualString(t, expectedString, actualString)
102 | }
103 |
104 | func TestNewMPDMultiPeriod(t *testing.T) {
105 | m := NewMPD(DASH_PROFILE_LIVE, VALID_MEDIA_PRESENTATION_DURATION, VALID_MIN_BUFFER_TIME,
106 | AttrAvailabilityStartTime(VALID_AVAILABILITY_START_TIME))
107 | require.NotNil(t, m)
108 | for i := 0; i < 2; i++ {
109 | period := m.AddNewPeriod()
110 | period.ID = strconv.Itoa(i)
111 | }
112 |
113 | expectedMPD := &MPD{
114 | XMLNs: Strptr("urn:mpeg:dash:schema:mpd:2011"),
115 | Profiles: Strptr((string)(DASH_PROFILE_LIVE)),
116 | Type: Strptr("static"),
117 | MediaPresentationDuration: Strptr(VALID_MEDIA_PRESENTATION_DURATION),
118 | MinBufferTime: Strptr(VALID_MIN_BUFFER_TIME),
119 | AvailabilityStartTime: Strptr(VALID_AVAILABILITY_START_TIME),
120 | period: nil,
121 | Periods: []*Period{{ID: "0"}, {ID: "1"}},
122 | }
123 |
124 | expectedString, err := expectedMPD.WriteToString()
125 | require.NoError(t, err)
126 | actualString, err := m.WriteToString()
127 | require.NoError(t, err)
128 |
129 | require.EqualString(t, expectedString, actualString)
130 | }
131 |
132 | func TestContentProtection_ImplementsInterface(t *testing.T) {
133 | cp := (*ContentProtectioner)(nil)
134 | require.Implements(t, cp, &ContentProtection{})
135 | require.Implements(t, cp, ContentProtection{})
136 | }
137 |
138 | func TestCENCContentProtection_ImplementsInterface(t *testing.T) {
139 | cp := (*ContentProtectioner)(nil)
140 | require.Implements(t, cp, &CENCContentProtection{})
141 | require.Implements(t, cp, CENCContentProtection{})
142 | }
143 |
144 | func TestPlayreadyContentProtection_ImplementsInterface(t *testing.T) {
145 | cp := (*ContentProtectioner)(nil)
146 | require.Implements(t, cp, &PlayreadyContentProtection{})
147 | require.Implements(t, cp, PlayreadyContentProtection{})
148 | }
149 |
150 | func TestWidevineContentProtection_ImplementsInterface(t *testing.T) {
151 | cp := (*ContentProtectioner)(nil)
152 | require.Implements(t, cp, &WidevineContentProtection{})
153 | require.Implements(t, cp, WidevineContentProtection{})
154 | }
155 |
156 | func TestNewMPDLiveWithBaseURLInMPD(t *testing.T) {
157 | m := NewMPD(DASH_PROFILE_LIVE, VALID_MEDIA_PRESENTATION_DURATION, VALID_MIN_BUFFER_TIME)
158 | m.BaseURL = []string{VALID_BASE_URL_VIDEO}
159 | require.NotNil(t, m)
160 | expectedMPD := &MPD{
161 | XMLNs: Strptr("urn:mpeg:dash:schema:mpd:2011"),
162 | Profiles: Strptr((string)(DASH_PROFILE_LIVE)),
163 | Type: Strptr("static"),
164 | MediaPresentationDuration: Strptr(VALID_MEDIA_PRESENTATION_DURATION),
165 | MinBufferTime: Strptr(VALID_MIN_BUFFER_TIME),
166 | period: &Period{},
167 | Periods: []*Period{{}},
168 | BaseURL: []string{VALID_BASE_URL_VIDEO},
169 | }
170 |
171 | expectedString, err := expectedMPD.WriteToString()
172 | require.NoError(t, err)
173 | actualString, err := m.WriteToString()
174 | require.NoError(t, err)
175 |
176 | require.EqualString(t, expectedString, actualString)
177 | }
178 |
179 | func TestNewMPDLiveWithBaseURLInPeriod(t *testing.T) {
180 | m := NewMPD(DASH_PROFILE_LIVE, VALID_MEDIA_PRESENTATION_DURATION, VALID_MIN_BUFFER_TIME)
181 | m.period.BaseURL = []string{VALID_BASE_URL_VIDEO}
182 | require.NotNil(t, m)
183 | period := &Period{
184 | BaseURL: []string{VALID_BASE_URL_VIDEO},
185 | }
186 | expectedMPD := &MPD{
187 | XMLNs: Strptr("urn:mpeg:dash:schema:mpd:2011"),
188 | Profiles: Strptr((string)(DASH_PROFILE_LIVE)),
189 | Type: Strptr("static"),
190 | MediaPresentationDuration: Strptr(VALID_MEDIA_PRESENTATION_DURATION),
191 | MinBufferTime: Strptr(VALID_MIN_BUFFER_TIME),
192 | period: period,
193 | Periods: []*Period{period},
194 | }
195 |
196 | expectedString, err := expectedMPD.WriteToString()
197 | require.NoError(t, err)
198 | actualString, err := m.WriteToString()
199 | require.NoError(t, err)
200 |
201 | require.EqualString(t, expectedString, actualString)
202 | }
203 |
204 | func TestNewMPDHbbTV(t *testing.T) {
205 | m := NewMPD(DASH_PROFILE_HBBTV_1_5_LIVE, VALID_MEDIA_PRESENTATION_DURATION, VALID_MIN_BUFFER_TIME)
206 | require.NotNil(t, m)
207 | expectedMPD := &MPD{
208 | XMLNs: Strptr("urn:mpeg:dash:schema:mpd:2011"),
209 | Profiles: Strptr((string)(DASH_PROFILE_HBBTV_1_5_LIVE)),
210 | Type: Strptr("static"),
211 | MediaPresentationDuration: Strptr(VALID_MEDIA_PRESENTATION_DURATION),
212 | MinBufferTime: Strptr(VALID_MIN_BUFFER_TIME),
213 | period: &Period{},
214 | Periods: []*Period{{}},
215 | }
216 |
217 | expectedString, err := expectedMPD.WriteToString()
218 | require.NoError(t, err)
219 | actualString, err := m.WriteToString()
220 | require.NoError(t, err)
221 |
222 | require.EqualString(t, expectedString, actualString)
223 | }
224 |
225 | func TestNewMPDOnDemand(t *testing.T) {
226 | m := NewMPD(DASH_PROFILE_ONDEMAND, VALID_MEDIA_PRESENTATION_DURATION, VALID_MIN_BUFFER_TIME)
227 | require.NotNil(t, m)
228 | expectedMPD := &MPD{
229 | XMLNs: Strptr("urn:mpeg:dash:schema:mpd:2011"),
230 | Profiles: Strptr((string)(DASH_PROFILE_ONDEMAND)),
231 | Type: Strptr("static"),
232 | MediaPresentationDuration: Strptr(VALID_MEDIA_PRESENTATION_DURATION),
233 | MinBufferTime: Strptr(VALID_MIN_BUFFER_TIME),
234 | period: &Period{},
235 | Periods: []*Period{{}},
236 | }
237 |
238 | expectedString, err := expectedMPD.WriteToString()
239 | require.NoError(t, err)
240 | actualString, err := m.WriteToString()
241 | require.NoError(t, err)
242 |
243 | require.EqualString(t, expectedString, actualString)
244 | }
245 |
246 | func TestAddAdaptationSetErrorNil(t *testing.T) {
247 | m := NewMPD(DASH_PROFILE_LIVE, VALID_MEDIA_PRESENTATION_DURATION, VALID_MIN_BUFFER_TIME)
248 |
249 | err := m.period.addAdaptationSet(nil)
250 | require.NotNil(t, err)
251 | require.EqualErr(t, ErrAdaptationSetNil, err)
252 | }
253 |
254 | type TestProprietaryContentProtection struct {
255 | ContentProtectionMarshal
256 | TestAttrA string `xml:"a,attr,omitempty"`
257 | TestAttrB string `xml:"b,attr,omitempty"`
258 | }
259 |
260 | func (s *TestProprietaryContentProtection) ContentProtected() {}
261 |
262 | func TestAddNewContentProtectionRootErrorInvalidLengthDefaultKID(t *testing.T) {
263 | m := NewMPD(DASH_PROFILE_LIVE, VALID_MEDIA_PRESENTATION_DURATION, VALID_MIN_BUFFER_TIME)
264 | s, _ := m.AddNewAdaptationSetVideoWithID("7357", DASH_MIME_TYPE_VIDEO_MP4, VALID_SCAN_TYPE, VALID_SEGMENT_ALIGNMENT, VALID_START_WITH_SAP)
265 |
266 | cp, err := s.AddNewContentProtectionRoot("invalidkid")
267 | require.NotNil(t, err)
268 | require.EqualErr(t, ErrInvalidDefaultKID, err)
269 | require.Nil(t, cp)
270 | }
271 |
272 | func TestAddNewContentProtectionRootErrorEmptyDefaultKID(t *testing.T) {
273 | m := NewMPD(DASH_PROFILE_LIVE, VALID_MEDIA_PRESENTATION_DURATION, VALID_MIN_BUFFER_TIME)
274 | s, _ := m.AddNewAdaptationSetVideoWithID("7357", DASH_MIME_TYPE_VIDEO_MP4, VALID_SCAN_TYPE, VALID_SEGMENT_ALIGNMENT, VALID_START_WITH_SAP)
275 |
276 | cp, err := s.AddNewContentProtectionRoot("")
277 | require.NotNil(t, err)
278 | require.EqualErr(t, ErrInvalidDefaultKID, err)
279 | require.Nil(t, cp)
280 | }
281 |
282 | func TestAddNewContentProtectionSchemePlayreadyErrorEmptyPRO(t *testing.T) {
283 | m := NewMPD(DASH_PROFILE_LIVE, VALID_MEDIA_PRESENTATION_DURATION, VALID_MIN_BUFFER_TIME)
284 | s, _ := m.AddNewAdaptationSetVideoWithID("7357", DASH_MIME_TYPE_VIDEO_MP4, VALID_SCAN_TYPE, VALID_SEGMENT_ALIGNMENT, VALID_START_WITH_SAP)
285 |
286 | cp, err := s.AddNewContentProtectionSchemePlayready("")
287 | require.NotNil(t, err)
288 | require.EqualErr(t, ErrPROEmpty, err)
289 | require.Nil(t, cp)
290 | }
291 |
292 | func TestAddNewContentProtectionSchemePlayreadyV10ErrorEmptyPRO(t *testing.T) {
293 | m := NewMPD(DASH_PROFILE_LIVE, VALID_MEDIA_PRESENTATION_DURATION, VALID_MIN_BUFFER_TIME)
294 | s, _ := m.AddNewAdaptationSetVideoWithID("7357", DASH_MIME_TYPE_VIDEO_MP4, VALID_SCAN_TYPE, VALID_SEGMENT_ALIGNMENT, VALID_START_WITH_SAP)
295 |
296 | cp, err := s.AddNewContentProtectionSchemePlayreadyV10("")
297 | require.NotNil(t, err)
298 | require.EqualErr(t, ErrPROEmpty, err)
299 | require.Nil(t, cp)
300 | }
301 |
302 | func TestSetNewSegmentTemplate(t *testing.T) {
303 | m := NewMPD(DASH_PROFILE_LIVE, VALID_MEDIA_PRESENTATION_DURATION, VALID_MIN_BUFFER_TIME)
304 | audioAS, _ := m.AddNewAdaptationSetAudioWithID("7357", DASH_MIME_TYPE_AUDIO_MP4, VALID_SEGMENT_ALIGNMENT, VALID_START_WITH_SAP, VALID_LANG)
305 | st, err := audioAS.SetNewSegmentTemplate(VALID_DURATION, VALID_INIT_PATH_AUDIO, VALID_MEDIA_PATH_AUDIO, VALID_START_NUMBER, VALID_TIMESCALE)
306 | require.NotNil(t, st)
307 | require.NoError(t, err)
308 | }
309 |
310 | func TestSetNewSegmentTemplateErrorNoDASHProfile(t *testing.T) {
311 | m := &MPD{
312 | XMLNs: Strptr("urn:mpeg:dash:schema:mpd:2011"),
313 | Profiles: nil,
314 | Type: Strptr("static"),
315 | MediaPresentationDuration: Strptr(VALID_MEDIA_PRESENTATION_DURATION),
316 | MinBufferTime: Strptr(VALID_MIN_BUFFER_TIME),
317 | period: &Period{},
318 | }
319 | audioAS, _ := m.AddNewAdaptationSetAudioWithID("7357", DASH_MIME_TYPE_AUDIO_MP4, VALID_SEGMENT_ALIGNMENT, VALID_START_WITH_SAP, VALID_LANG)
320 | _, _ = audioAS.SetNewSegmentTemplate(VALID_DURATION, VALID_INIT_PATH_AUDIO, VALID_MEDIA_PATH_AUDIO, VALID_START_NUMBER, VALID_TIMESCALE)
321 | err := m.Validate()
322 | require.EqualErr(t, ErrNoDASHProfileSet, err)
323 | }
324 |
325 | func TestAddRepresentationAudio(t *testing.T) {
326 | m := NewMPD(DASH_PROFILE_LIVE, VALID_MEDIA_PRESENTATION_DURATION, VALID_MIN_BUFFER_TIME)
327 | audioAS, _ := m.AddNewAdaptationSetAudioWithID("7357", DASH_MIME_TYPE_AUDIO_MP4, VALID_SEGMENT_ALIGNMENT, VALID_START_WITH_SAP, VALID_LANG)
328 |
329 | r, err := audioAS.AddNewRepresentationAudio(VALID_AUDIO_SAMPLE_RATE, VALID_AUDIO_BITRATE, VALID_AUDIO_CODEC, VALID_AUDIO_ID)
330 |
331 | require.NotNil(t, r)
332 | require.NoError(t, err)
333 | }
334 |
335 | func TestAddAudioChannelConfiguration(t *testing.T) {
336 | m := NewMPD(DASH_PROFILE_HBBTV_1_5_LIVE, VALID_MEDIA_PRESENTATION_DURATION, VALID_MIN_BUFFER_TIME)
337 | audioAS, _ := m.AddNewAdaptationSetAudioWithID("7357", DASH_MIME_TYPE_AUDIO_MP4, VALID_SEGMENT_ALIGNMENT, VALID_START_WITH_SAP, VALID_LANG)
338 |
339 | r, _ := audioAS.AddNewRepresentationAudio(VALID_AUDIO_SAMPLE_RATE, VALID_AUDIO_BITRATE, VALID_AUDIO_CODEC, VALID_AUDIO_ID)
340 |
341 | acc, err := r.AddNewAudioChannelConfiguration(AUDIO_CHANNEL_CONFIGURATION_MPEG_DASH, "2")
342 |
343 | require.NotNil(t, acc)
344 | require.NoError(t, err)
345 | }
346 |
347 | func TestAddRepresentationVideo(t *testing.T) {
348 | m := NewMPD(DASH_PROFILE_LIVE, VALID_MEDIA_PRESENTATION_DURATION, VALID_MIN_BUFFER_TIME)
349 | videoAS, _ := m.AddNewAdaptationSetVideoWithID("7357", DASH_MIME_TYPE_VIDEO_MP4, VALID_SCAN_TYPE, VALID_SEGMENT_ALIGNMENT, VALID_START_WITH_SAP)
350 |
351 | r, err := videoAS.AddNewRepresentationVideo(VALID_VIDEO_BITRATE, VALID_VIDEO_CODEC, VALID_VIDEO_ID, VALID_VIDEO_FRAMERATE, VALID_VIDEO_WIDTH, VALID_VIDEO_HEIGHT)
352 |
353 | require.NotNil(t, r)
354 | require.NoError(t, err)
355 | }
356 |
357 | func TestAddRepresentationSubtitle(t *testing.T) {
358 | m := NewMPD(DASH_PROFILE_LIVE, VALID_MEDIA_PRESENTATION_DURATION, VALID_MIN_BUFFER_TIME)
359 |
360 | subtitleAS, _ := m.AddNewAdaptationSetSubtitleWithID("7357", DASH_MIME_TYPE_SUBTITLE_VTT, VALID_LANG, VALID_SUBTITLE_LABEL)
361 |
362 | r, err := subtitleAS.AddNewRepresentationSubtitle(VALID_SUBTITLE_BANDWIDTH, VALID_SUBTITLE_ID)
363 |
364 | require.NotNil(t, r)
365 | require.NoError(t, err)
366 | }
367 |
368 | func TestAddRepresentationErrorNil(t *testing.T) {
369 | m := NewMPD(DASH_PROFILE_LIVE, VALID_MEDIA_PRESENTATION_DURATION, VALID_MIN_BUFFER_TIME)
370 | videoAS, _ := m.AddNewAdaptationSetVideoWithID("7357", DASH_MIME_TYPE_VIDEO_MP4, VALID_SCAN_TYPE, VALID_SEGMENT_ALIGNMENT, VALID_START_WITH_SAP)
371 |
372 | err := videoAS.addRepresentation(nil)
373 | require.NotNil(t, err)
374 | require.EqualErr(t, ErrRepresentationNil, err)
375 | }
376 |
377 | func TestAddRole(t *testing.T) {
378 | m := NewMPD(DASH_PROFILE_LIVE, VALID_MEDIA_PRESENTATION_DURATION, VALID_MIN_BUFFER_TIME)
379 | audioAS, _ := m.AddNewAdaptationSetAudioWithID("7357", DASH_MIME_TYPE_AUDIO_MP4, VALID_SEGMENT_ALIGNMENT, VALID_START_WITH_SAP, VALID_LANG)
380 |
381 | r, err := audioAS.AddNewRole("urn:mpeg:dash:role:2011", VALID_ROLE)
382 |
383 | require.NotNil(t, r)
384 | require.NoError(t, err)
385 | }
386 |
387 | func TestSetSegmentTemplateErrorNil(t *testing.T) {
388 | m := NewMPD(DASH_PROFILE_LIVE, VALID_MEDIA_PRESENTATION_DURATION, VALID_MIN_BUFFER_TIME)
389 | audioAS, _ := m.AddNewAdaptationSetAudioWithID("7357", DASH_MIME_TYPE_AUDIO_MP4, VALID_SEGMENT_ALIGNMENT, VALID_START_WITH_SAP, VALID_LANG)
390 | err := audioAS.setSegmentTemplate(nil)
391 | require.NotNil(t, err)
392 | require.EqualErr(t, ErrSegmentTemplateNil, err)
393 | }
394 |
395 | func TestSetNewBaseURLVideo(t *testing.T) {
396 | m := NewMPD(DASH_PROFILE_ONDEMAND, VALID_MEDIA_PRESENTATION_DURATION, VALID_MIN_BUFFER_TIME)
397 | videoAS, _ := m.AddNewAdaptationSetVideoWithID("7357", DASH_MIME_TYPE_VIDEO_MP4, VALID_SCAN_TYPE, VALID_SEGMENT_ALIGNMENT, VALID_START_WITH_SAP)
398 |
399 | r, _ := videoAS.AddNewRepresentationVideo(VALID_VIDEO_BITRATE, VALID_VIDEO_CODEC, VALID_VIDEO_ID, VALID_VIDEO_FRAMERATE, VALID_VIDEO_WIDTH, VALID_VIDEO_HEIGHT)
400 |
401 | err := r.SetNewBaseURL(VALID_BASE_URL_VIDEO)
402 |
403 | require.NoError(t, err)
404 | }
405 |
406 | func TestAddNewBaseURLVideo(t *testing.T) {
407 | m := NewMPD(DASH_PROFILE_ONDEMAND, VALID_MEDIA_PRESENTATION_DURATION, VALID_MIN_BUFFER_TIME)
408 | videoAS, _ := m.AddNewAdaptationSetVideoWithID("7357", DASH_MIME_TYPE_VIDEO_MP4, VALID_SCAN_TYPE, VALID_SEGMENT_ALIGNMENT, VALID_START_WITH_SAP)
409 |
410 | r, _ := videoAS.AddNewRepresentationVideo(VALID_VIDEO_BITRATE, VALID_VIDEO_CODEC, VALID_VIDEO_ID, VALID_VIDEO_FRAMERATE, VALID_VIDEO_WIDTH, VALID_VIDEO_HEIGHT)
411 |
412 | err := r.AddNewBaseURL("./")
413 | require.NoError(t, err)
414 |
415 | err = r.AddNewBaseURL("../a/")
416 | require.NoError(t, err)
417 |
418 | err = r.AddNewBaseURL("../b/")
419 | require.NoError(t, err)
420 |
421 | require.EqualStringSlice(t, []string{"./", "../a/", "../b/"}, r.BaseURL)
422 | }
423 |
424 | func TestSetNewBaseURLSubtitle(t *testing.T) {
425 | m := NewMPD(DASH_PROFILE_ONDEMAND, VALID_MEDIA_PRESENTATION_DURATION, VALID_MIN_BUFFER_TIME)
426 | subtitleAS, _ := m.AddNewAdaptationSetSubtitleWithID("7357", DASH_MIME_TYPE_SUBTITLE_VTT, VALID_LANG, VALID_SUBTITLE_LABEL)
427 |
428 | r, _ := subtitleAS.AddNewRepresentationSubtitle(VALID_SUBTITLE_BANDWIDTH, VALID_SUBTITLE_ID)
429 |
430 | err := r.SetNewBaseURL(VALID_SUBTITLE_URL)
431 |
432 | require.NoError(t, err)
433 | }
434 |
435 | func TestSetNewBaseURLErrorNoDASHProfile(t *testing.T) {
436 | m := &MPD{
437 | XMLNs: Strptr("urn:mpeg:dash:schema:mpd:2011"),
438 | Profiles: nil,
439 | Type: Strptr("static"),
440 | MediaPresentationDuration: Strptr(VALID_MEDIA_PRESENTATION_DURATION),
441 | MinBufferTime: Strptr(VALID_MIN_BUFFER_TIME),
442 | period: &Period{},
443 | }
444 | videoAS, _ := m.AddNewAdaptationSetVideoWithID("7357", DASH_MIME_TYPE_VIDEO_MP4, VALID_SCAN_TYPE, VALID_SEGMENT_ALIGNMENT, VALID_START_WITH_SAP)
445 |
446 | r, _ := videoAS.AddNewRepresentationVideo(VALID_VIDEO_BITRATE, VALID_VIDEO_CODEC, VALID_VIDEO_ID, VALID_VIDEO_FRAMERATE, VALID_VIDEO_WIDTH, VALID_VIDEO_HEIGHT)
447 |
448 | _ = r.SetNewBaseURL(VALID_BASE_URL_VIDEO)
449 | err := m.Validate()
450 |
451 | require.NotNil(t, err)
452 | require.EqualErr(t, ErrNoDASHProfileSet, err)
453 | }
454 |
455 | func TestSetNewBaseURLErrorEmpty(t *testing.T) {
456 | m := NewMPD(DASH_PROFILE_ONDEMAND, VALID_MEDIA_PRESENTATION_DURATION, VALID_MIN_BUFFER_TIME)
457 | videoAS, _ := m.AddNewAdaptationSetVideoWithID("7357", DASH_MIME_TYPE_VIDEO_MP4, VALID_SCAN_TYPE, VALID_SEGMENT_ALIGNMENT, VALID_START_WITH_SAP)
458 |
459 | r, _ := videoAS.AddNewRepresentationVideo(VALID_VIDEO_BITRATE, VALID_VIDEO_CODEC, VALID_VIDEO_ID, VALID_VIDEO_FRAMERATE, VALID_VIDEO_WIDTH, VALID_VIDEO_HEIGHT)
460 |
461 | err := r.SetNewBaseURL("")
462 |
463 | require.NotNil(t, err)
464 | require.EqualErr(t, ErrBaseURLEmpty, err)
465 | }
466 |
467 | func TestSetNewSegmentBase(t *testing.T) {
468 | m := NewMPD(DASH_PROFILE_ONDEMAND, VALID_MEDIA_PRESENTATION_DURATION, VALID_MIN_BUFFER_TIME)
469 | videoAS, _ := m.AddNewAdaptationSetVideoWithID("7357", DASH_MIME_TYPE_VIDEO_MP4, VALID_SCAN_TYPE, VALID_SEGMENT_ALIGNMENT, VALID_START_WITH_SAP)
470 |
471 | r, _ := videoAS.AddNewRepresentationVideo(VALID_VIDEO_BITRATE, VALID_VIDEO_CODEC, VALID_VIDEO_ID, VALID_VIDEO_FRAMERATE, VALID_VIDEO_WIDTH, VALID_VIDEO_HEIGHT)
472 |
473 | sb, err := r.AddNewSegmentBase(VALID_INDEX_RANGE, VALID_INIT_RANGE)
474 | require.NotNil(t, sb)
475 | require.NoError(t, err)
476 | }
477 |
478 | func TestSetNewSegmentBaseErrorNoDASHProfile(t *testing.T) {
479 | m := &MPD{
480 | XMLNs: Strptr("urn:mpeg:dash:schema:mpd:2011"),
481 | Profiles: nil,
482 | Type: Strptr("static"),
483 | MediaPresentationDuration: Strptr(VALID_MEDIA_PRESENTATION_DURATION),
484 | MinBufferTime: Strptr(VALID_MIN_BUFFER_TIME),
485 | period: &Period{},
486 | }
487 | videoAS, _ := m.AddNewAdaptationSetVideoWithID("7357", DASH_MIME_TYPE_VIDEO_MP4, VALID_SCAN_TYPE, VALID_SEGMENT_ALIGNMENT, VALID_START_WITH_SAP)
488 |
489 | r, _ := videoAS.AddNewRepresentationVideo(VALID_VIDEO_BITRATE, VALID_VIDEO_CODEC, VALID_VIDEO_ID, VALID_VIDEO_FRAMERATE, VALID_VIDEO_WIDTH, VALID_VIDEO_HEIGHT)
490 |
491 | _, _ = r.AddNewSegmentBase(VALID_INDEX_RANGE, VALID_INIT_RANGE)
492 |
493 | err := m.Validate()
494 | require.EqualErr(t, ErrNoDASHProfileSet, err)
495 | }
496 |
497 | func TestSetSegmentBaseErrorNil(t *testing.T) {
498 | m := NewMPD(DASH_PROFILE_ONDEMAND, VALID_MEDIA_PRESENTATION_DURATION, VALID_MIN_BUFFER_TIME)
499 | videoAS, _ := m.AddNewAdaptationSetVideoWithID("7357", DASH_MIME_TYPE_VIDEO_MP4, VALID_SCAN_TYPE, VALID_SEGMENT_ALIGNMENT, VALID_START_WITH_SAP)
500 |
501 | r, _ := videoAS.AddNewRepresentationVideo(VALID_VIDEO_BITRATE, VALID_VIDEO_CODEC, VALID_VIDEO_ID, VALID_VIDEO_FRAMERATE, VALID_VIDEO_WIDTH, VALID_VIDEO_HEIGHT)
502 |
503 | err := r.setSegmentBase(nil)
504 | require.NotNil(t, err)
505 | require.EqualErr(t, ErrSegmentBaseNil, err)
506 | }
507 |
508 | func getValidWVHeaderBytes() []byte {
509 | wvHeader, err := base64.StdEncoding.DecodeString(VALID_WV_HEADER)
510 | if err != nil {
511 | panic(err.Error())
512 | }
513 | return wvHeader
514 | }
515 |
516 | func TestAddNewAccessibilityElement(t *testing.T) {
517 | m := NewMPD(DASH_PROFILE_LIVE, VALID_MEDIA_PRESENTATION_DURATION, VALID_MIN_BUFFER_TIME)
518 | audioAS, err := m.AddNewAdaptationSetAudioWithID("7357", DASH_MIME_TYPE_AUDIO_MP4, VALID_SEGMENT_ALIGNMENT,
519 | VALID_START_WITH_SAP, VALID_LANG)
520 | if err != nil {
521 | t.Errorf("AddNewAccessibilityElement() error adding audio adaptation set: %v", err)
522 | return
523 | }
524 |
525 | _, err = audioAS.AddNewAccessibilityElement(ACCESSIBILITY_ELEMENT_SCHEME_DESCRIPTIVE_AUDIO, "1")
526 | if err != nil {
527 | t.Errorf("AddNewAccessibilityElement() error adding accessibility element: %v", err)
528 | return
529 | }
530 |
531 | if g, e := len(audioAS.AccessibilityElems), 1; g != e {
532 | t.Errorf("AddNewAccessibilityElement() wrong number of accessibility elements, got: %d, expected: %d",
533 | g, e)
534 | return
535 | }
536 |
537 | elem := audioAS.AccessibilityElems[0]
538 |
539 | require.EqualStringPtr(t, Strptr((string)(ACCESSIBILITY_ELEMENT_SCHEME_DESCRIPTIVE_AUDIO)), elem.SchemeIdUri)
540 | require.EqualStringPtr(t, Strptr("1"), elem.Value)
541 | }
542 |
543 | func TestLocationWriteToString(t *testing.T) {
544 | m := &MPD{
545 | XMLNs: Strptr("urn:mpeg:dash:schema:mpd:2011"),
546 | Profiles: Strptr((string)(DASH_PROFILE_LIVE)),
547 | Type: Strptr("dynamic"),
548 | AvailabilityStartTime: Strptr(VALID_AVAILABILITY_START_TIME),
549 | MinimumUpdatePeriod: Strptr(VALID_MINIMUM_UPDATE_PERIOD),
550 | PublishTime: Strptr(VALID_AVAILABILITY_START_TIME),
551 | Location: VALID_LOCATION,
552 | }
553 |
554 | got, err := m.WriteToString()
555 | require.NoError(t, err)
556 |
557 | testfixtures.CompareFixture(t, "fixtures/location.mpd", got)
558 | }
559 |
560 | func TestReadWriteIdentical(t *testing.T) {
561 | const fixtures = "fixtures/"
562 | var (
563 | skipFixtures = map[string]struct{}{"invalid.mpd": {}}
564 | matches, err = filepath.Glob(fixtures + "*.mpd")
565 | )
566 | require.NoError(t, err)
567 | for _, file := range matches {
568 | file := filepath.Base(file)
569 | if _, ok := skipFixtures[file]; ok {
570 | continue
571 | }
572 | t.Run(file, func(t *testing.T) {
573 | m, err := ReadFromFile(fixtures + file)
574 | require.NoError(t, err)
575 | got, err := m.WriteToString()
576 | require.NoError(t, err)
577 | testfixtures.CompareFixture(t, fixtures+file, got)
578 | })
579 | }
580 | }
581 |
582 | func TestSetInbandEventStream(t *testing.T) {
583 | var (
584 | err error
585 | got string
586 | )
587 |
588 | m := NewMPD(DASH_PROFILE_LIVE, VALID_MEDIA_PRESENTATION_DURATION, VALID_MIN_BUFFER_TIME)
589 | videoAS, _ := m.AddNewAdaptationSetVideoWithID("7357", DASH_MIME_TYPE_VIDEO_MP4, VALID_SCAN_TYPE, VALID_SEGMENT_ALIGNMENT, VALID_START_WITH_SAP)
590 | err = videoAS.AddNewInbandEventStream(VALID_SCHEME_ID_URI, "0")
591 |
592 | require.NoError(t, err)
593 |
594 | r, _ := videoAS.AddNewRepresentationVideo(VALID_VIDEO_BITRATE, VALID_VIDEO_CODEC, VALID_VIDEO_ID, VALID_VIDEO_FRAMERATE, VALID_VIDEO_WIDTH, VALID_VIDEO_HEIGHT)
595 | err = r.AddNewInbandEventStream(VALID_SCHEME_ID_URI, "1")
596 |
597 | require.NoError(t, err)
598 |
599 | got, err = m.WriteToString()
600 | require.NoError(t, err)
601 |
602 | testfixtures.CompareFixture(t, "fixtures/inband_event_stream.mpd", got)
603 | }
604 |
605 | func TestSetInbandEventStreamError(t *testing.T) {
606 | m := NewMPD(DASH_PROFILE_LIVE, VALID_MEDIA_PRESENTATION_DURATION, VALID_MIN_BUFFER_TIME)
607 | videoAS, _ := m.AddNewAdaptationSetVideoWithID("7357", DASH_MIME_TYPE_VIDEO_MP4, VALID_SCAN_TYPE, VALID_SEGMENT_ALIGNMENT, VALID_START_WITH_SAP)
608 | err := videoAS.AddNewInbandEventStream("", "")
609 |
610 | require.EqualErr(t, ErrInbandEventStreamSchemeUriEmpty, err)
611 | }
612 |
--------------------------------------------------------------------------------
/mpd/pssh.go:
--------------------------------------------------------------------------------
1 | package mpd
2 |
3 | import (
4 | "bytes"
5 | "encoding/binary"
6 | "fmt"
7 | )
8 |
9 | func MakePSSHBox(systemID, payload []byte) ([]byte, error) {
10 | if len(systemID) != 16 {
11 | return nil, fmt.Errorf("SystemID must be 16 bytes, was: %d", len(systemID))
12 | }
13 |
14 | psshBuf := &bytes.Buffer{}
15 | size := uint32(12 + 16 + 4 + len(payload)) // 3 uint32s, systemID, "pssh" string and payload
16 | if err := binary.Write(psshBuf, binary.BigEndian, size); err != nil {
17 | return nil, err
18 | }
19 |
20 | if err := binary.Write(psshBuf, binary.BigEndian, []byte("pssh")); err != nil {
21 | return nil, err
22 | }
23 |
24 | if err := binary.Write(psshBuf, binary.BigEndian, uint32(0)); err != nil {
25 | return nil, err
26 | }
27 |
28 | if _, err := psshBuf.Write(systemID); err != nil {
29 | return nil, err
30 | }
31 |
32 | if err := binary.Write(psshBuf, binary.BigEndian, uint32(len(payload))); err != nil {
33 | return nil, err
34 | }
35 |
36 | if _, err := psshBuf.Write(payload); err != nil {
37 | return nil, err
38 | }
39 |
40 | return psshBuf.Bytes(), nil
41 | }
42 |
--------------------------------------------------------------------------------
/mpd/pssh_test.go:
--------------------------------------------------------------------------------
1 | package mpd
2 |
3 | import (
4 | "encoding/base64"
5 | "encoding/hex"
6 | "github.com/zencoder/go-dash/v3/helpers/require"
7 | "testing"
8 | )
9 |
10 | func TestMakePSSHBox_Widevine(t *testing.T) {
11 | expectedPSSH, err := base64.StdEncoding.DecodeString("AAAAYXBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAAEEIARIQWr3VL1VKTyq40GH3YUJRVRoIY2FzdGxhYnMiGFdyM1ZMMVZLVHlxNDBHSDNZVUpSVlE9PTIHZGVmYXVsdA==")
12 | if err != nil {
13 | panic(err.Error())
14 | }
15 |
16 | payload, err := base64.StdEncoding.DecodeString(VALID_WV_HEADER)
17 | if err != nil {
18 | panic(err.Error())
19 | }
20 |
21 | wvSystemID, err := hex.DecodeString(CONTENT_PROTECTION_WIDEVINE_SCHEME_HEX)
22 | if err != nil {
23 | panic(err.Error())
24 | }
25 |
26 | psshBox, err := MakePSSHBox(wvSystemID, payload)
27 | require.NoError(t, err)
28 |
29 | require.EqualString(t, string(expectedPSSH), string(psshBox))
30 | }
31 |
32 | func TestMakePSSHBox_Playready(t *testing.T) {
33 | expectedPSSH, err := base64.StdEncoding.DecodeString("AAACJnBzc2gAAAAAmgTweZhAQoarkuZb4IhflQAAAgYGAgAAAQABAPwBPABXAFIATQBIAEUAQQBEAEUAUgAgAHgAbQBsAG4AcwA9ACIAaAB0AHQAcAA6AC8ALwBzAGMAaABlAG0AYQBzAC4AbQBpAGMAcgBvAHMAbwBmAHQALgBjAG8AbQAvAEQAUgBNAC8AMgAwADAANwAvADAAMwAvAFAAbABhAHkAUgBlAGEAZAB5AEgAZQBhAGQAZQByACIAIAB2AGUAcgBzAGkAbwBuAD0AIgA0AC4AMAAuADAALgAwACIAPgA8AEQAQQBUAEEAPgA8AFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBFAFkATABFAE4APgAxADYAPAAvAEsARQBZAEwARQBOAD4APABBAEwARwBJAEQAPgBBAEUAUwBDAFQAUgA8AC8AQQBMAEcASQBEAD4APAAvAFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBJAEQAPgBMADkAVwA5AFcAawBwAFYASwBrACsANAAwAEcASAAzAFkAVQBKAFIAVgBRAD0APQA8AC8ASwBJAEQAPgA8AEMASABFAEMASwBTAFUATQA+AEkASwB6AFkAMgBIAFoATABBAGwASQA9ADwALwBDAEgARQBDAEsAUwBVAE0APgA8AC8ARABBAFQAQQA+ADwALwBXAFIATQBIAEUAQQBEAEUAUgA+AA==")
34 | if err != nil {
35 | panic(err.Error())
36 | }
37 |
38 | // Base64 PRO
39 | payload, err := base64.StdEncoding.DecodeString(VALID_PLAYREADY_PRO)
40 | if err != nil {
41 | panic(err.Error())
42 | }
43 |
44 | wvSystemID, err := hex.DecodeString(CONTENT_PROTECTION_PLAYREADY_SCHEME_HEX)
45 | if err != nil {
46 | panic(err.Error())
47 | }
48 |
49 | psshBox, err := MakePSSHBox(wvSystemID, payload)
50 | require.NoError(t, err)
51 |
52 | require.EqualString(t, string(expectedPSSH), string(psshBox))
53 | }
54 |
55 | func TestMakePSSHBox_BadSystemID(t *testing.T) {
56 | _, err := MakePSSHBox([]byte("meaningless byte array"), nil)
57 | require.EqualError(t, err, "SystemID must be 16 bytes, was: 22")
58 | }
59 |
60 | func TestMakePSSHBox_NilSystemID(t *testing.T) {
61 | _, err := MakePSSHBox(nil, nil)
62 | require.EqualError(t, err, "SystemID must be 16 bytes, was: 0")
63 | }
64 |
--------------------------------------------------------------------------------
/mpd/scte35.go:
--------------------------------------------------------------------------------
1 | package mpd
2 |
3 | import (
4 | "encoding/xml"
5 | "sort"
6 |
7 | "github.com/Comcast/scte35-go/pkg/scte35"
8 | . "github.com/zencoder/go-dash/v3/helpers/ptrs"
9 | )
10 |
11 | const (
12 | SCTE352014SchemeIdUri = "urn:scte:scte35:2014:xml+bin"
13 | SCTE35352016Namespace = "http://www.scte.org/schemas/35/2016"
14 | )
15 |
16 | type Signal struct {
17 | XMLName xml.Name `xml:"Signal"`
18 | XMLNs *string `xml:"xmlns,attr,omitempty"`
19 | Namespace *string `xml:"namespace,attr,omitempty"`
20 | Binaries []Binary `xml:"Binary,omitempty"`
21 | }
22 |
23 | type Binary struct {
24 | XMLName xml.Name `xml:"Binary"`
25 | XMLNs *string `xml:"xmlns,attr,omitempty"`
26 | BinaryData *string `xml:",chardata"`
27 | }
28 |
29 | // SCTE35EventOption is used to create options to modify the scte35Break Event.
30 | type SCTE35EventOption func(scte35Break *Event)
31 |
32 | // AddNewSCTE35Break will create a new period with an empty SCTE-35 period
33 | // compliant with urn:scte:scte35:2014:xml+bin. This function accepts optional
34 | // SCTE35EventOptions to further modify the break.
35 | func (period *Period) AddNewSCTE35Break(
36 | timeScale uint,
37 | presentationTime uint64,
38 | id string,
39 | eventOptions ...SCTE35EventOption,
40 | ) {
41 | var eventStreams []EventStream
42 | if period.EventStreams != nil {
43 | eventStreams = period.EventStreams
44 | }
45 |
46 | var (
47 | scte35EventStream EventStream
48 | eventStreamIndex = -1
49 | )
50 |
51 | for i, eventStream := range eventStreams {
52 | if eventStream.SchemeIDURI == nil || *eventStream.SchemeIDURI != SCTE352014SchemeIdUri {
53 | continue
54 | }
55 |
56 | scte35EventStream = eventStream
57 | eventStreamIndex = i
58 | break
59 | }
60 |
61 | scte35Break := Event{
62 | ID: Strptr(id),
63 | PresentationTime: Uint64ptr(presentationTime),
64 | }
65 |
66 | for _, eventOption := range eventOptions {
67 | eventOption(&scte35Break)
68 | }
69 |
70 | scte35EventStream.Events = append(scte35EventStream.Events, scte35Break)
71 |
72 | sort.Sort(ByPresentationTime(scte35EventStream.Events))
73 |
74 | if eventStreamIndex != -1 {
75 | period.EventStreams[eventStreamIndex] = scte35EventStream
76 | return
77 | }
78 |
79 | scte35EventStream.SchemeIDURI = Strptr(SCTE352014SchemeIdUri)
80 | scte35EventStream.Timescale = Uintptr(timeScale)
81 | period.EventStreams = append(period.EventStreams, scte35EventStream)
82 | }
83 |
84 | func getBreakSignal(scte35Break *Event) Signal {
85 | var signal Signal
86 | if len(scte35Break.Signals) != 0 {
87 | signal = scte35Break.Signals[0]
88 | }
89 |
90 | return signal
91 | }
92 |
93 | // WithBodyBinary sets the provided body binary in the break. This will also
94 | // set the XMLNs schema to the SCTE-35 2014 Schema.
95 | func WithBodyBinary(bodyBinary string) SCTE35EventOption {
96 | return func(scte35Break *Event) {
97 | signal := getBreakSignal(scte35Break)
98 |
99 | signal.XMLNs = Strptr(SCTE352014SchemeIdUri)
100 | signal.Binaries = []Binary{
101 | {
102 | XMLNs: Strptr(SCTE352014SchemeIdUri),
103 | BinaryData: Strptr(bodyBinary),
104 | },
105 | }
106 |
107 | scte35Break.Signals = []Signal{signal}
108 | }
109 | }
110 |
111 | // WithNameSpace sets the provided namespace as the break's signal namespace.
112 | func WithNameSpace(nameSpace string) SCTE35EventOption {
113 | return func(scte35Break *Event) {
114 | signal := getBreakSignal(scte35Break)
115 |
116 | signal.Namespace = Strptr(nameSpace)
117 |
118 | scte35Break.Signals = []Signal{signal}
119 | }
120 | }
121 |
122 | // WithSpliceInfoSection takes the provided scte35.SpliceInfoSection, encodes it
123 | // and then sets it as the binary data for the break. This will also set the
124 | // XMLNs schema to the SCTE-35 2014 Schema.
125 | func WithSpliceInfoSection(spliceInfoSection *scte35.SpliceInfoSection) SCTE35EventOption {
126 | return func(scte35Break *Event) {
127 | signal := getBreakSignal(scte35Break)
128 |
129 | signal.XMLNs = Strptr(SCTE352014SchemeIdUri)
130 | signal.Binaries = []Binary{
131 | {
132 | XMLNs: Strptr(SCTE352014SchemeIdUri),
133 | BinaryData: Strptr(spliceInfoSection.Base64()),
134 | },
135 | }
136 |
137 | scte35Break.Signals = []Signal{signal}
138 | }
139 | }
140 |
141 | // WithSpliceInsertCommand creates a new scte35.SpliceInfoSection with the
142 | // splice_insert command type, and calls WithSpliceInfoSection.
143 | func WithSpliceInsertCommand() SCTE35EventOption {
144 | return func(scte35Break *Event) {
145 | spliceInfoSection := &scte35.SpliceInfoSection{
146 | SpliceCommand: scte35.NewSpliceCommand(scte35.SpliceInsertType),
147 | }
148 |
149 | WithSpliceInfoSection(spliceInfoSection)(scte35Break)
150 | }
151 | }
152 |
--------------------------------------------------------------------------------
/mpd/scte35_test.go:
--------------------------------------------------------------------------------
1 | package mpd
2 |
3 | import (
4 | "os"
5 | "strconv"
6 | "testing"
7 |
8 | "github.com/Comcast/scte35-go/pkg/scte35"
9 | "github.com/zencoder/go-dash/v3/helpers/require"
10 | )
11 |
12 | func TestPeriod_AddNewSCTE35Break(t *testing.T) {
13 | m := NewMPD(DASH_PROFILE_LIVE, VALID_MEDIA_PRESENTATION_DURATION, VALID_MIN_BUFFER_TIME,
14 | AttrAvailabilityStartTime(VALID_AVAILABILITY_START_TIME))
15 | require.NotNil(t, m)
16 |
17 | for i := 0; i < 3; i++ {
18 | period := m.AddNewPeriod()
19 | period.ID = strconv.Itoa(i)
20 | }
21 |
22 | m.Periods[1].AddNewSCTE35Break(VALID_EVENT_STREAM_TIMESCALE, 10000, "1")
23 |
24 | m.Periods[1].AddNewSCTE35Break(1, 20000, "3", WithNameSpace(SCTE35352016Namespace), WithBodyBinary("/DAgAAAAAAAAAP/wBQb+AABb0AAAABAAAAEAAQCA=="))
25 | m.Periods[1].AddNewSCTE35Break(1, 20001, "4", WithNameSpace(SCTE35352016Namespace), WithSpliceInsertCommand())
26 | m.Periods[1].AddNewSCTE35Break(VALID_EVENT_STREAM_TIMESCALE, 15000, "2", WithSpliceInfoSection(&scte35.SpliceInfoSection{SpliceCommand: scte35.NewSpliceCommand(scte35.SpliceScheduleType)}))
27 |
28 | expected, err := os.ReadFile("fixtures/scte35.mpd")
29 | require.NoError(t, err)
30 |
31 | actual, err := m.WriteToString()
32 | require.NoError(t, err)
33 |
34 | require.EqualString(t, string(expected), actual)
35 | }
36 |
--------------------------------------------------------------------------------
/mpd/segment.go:
--------------------------------------------------------------------------------
1 | package mpd
2 |
3 | type SegmentBase struct {
4 | Initialization *URL `xml:"Initialization,omitempty"`
5 | RepresentationIndex *URL `xml:"RepresentationIndex,omitempty"`
6 | Timescale *uint32 `xml:"timescale,attr,omitempty"`
7 | PresentationTimeOffset *uint64 `xml:"presentationTimeOffset,attr,omitempty"`
8 | IndexRange *string `xml:"indexRange,attr,omitempty"`
9 | IndexRangeExact *bool `xml:"indexRangeExact,attr,omitempty"`
10 | AvailabilityTimeOffset *float32 `xml:"availabilityTimeOffset,attr,omitempty"`
11 | AvailabilityTimeComplete *bool `xml:"availabilityTimeComplete,attr,omitempty"`
12 | }
13 |
14 | type MultipleSegmentBase struct {
15 | SegmentBase
16 | SegmentTimeline *SegmentTimeline `xml:"SegmentTimeline,omitempty"`
17 | BitstreamSwitching *URL `xml:"BitstreamSwitching,omitempty"`
18 | Duration *uint32 `xml:"duration,attr,omitempty"`
19 | StartNumber *uint32 `xml:"startNumber,attr,omitempty"`
20 | }
21 |
22 | type SegmentList struct {
23 | MultipleSegmentBase
24 | SegmentURLs []*SegmentURL `xml:"SegmentURL,omitempty"`
25 | }
26 |
27 | type SegmentURL struct {
28 | Media *string `xml:"media,attr,omitempty"`
29 | MediaRange *string `xml:"mediaRange,attr,omitempty"`
30 | Index *string `xml:"index,attr,omitempty"`
31 | IndexRange *string `xml:"indexRange,attr,omitempty"`
32 | }
33 |
34 | type SegmentTimeline struct {
35 | Segments []*SegmentTimelineSegment `xml:"S,omitempty"`
36 | }
37 |
38 | type SegmentTimelineSegment struct {
39 | StartTime *uint64 `xml:"t,attr,omitempty"`
40 | Duration uint64 `xml:"d,attr"`
41 | RepeatCount *int `xml:"r,attr,omitempty"`
42 | }
43 |
44 | type URL struct {
45 | SourceURL *string `xml:"sourceURL,attr,omitempty"`
46 | Range *string `xml:"range,attr,omitempty"`
47 | }
48 |
--------------------------------------------------------------------------------
/mpd/segment_list_test.go:
--------------------------------------------------------------------------------
1 | package mpd
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/zencoder/go-dash/v3/helpers/ptrs"
7 | "github.com/zencoder/go-dash/v3/helpers/require"
8 | "github.com/zencoder/go-dash/v3/helpers/testfixtures"
9 | )
10 |
11 | func TestSegmentListSerialization(t *testing.T) {
12 | m := getSegmentListMPD()
13 | xml, err := m.WriteToString()
14 | require.NoError(t, err)
15 | testfixtures.CompareFixture(t, "fixtures/segment_list.mpd", xml)
16 | }
17 |
18 | func TestSegmentListDeserialization(t *testing.T) {
19 | xml := testfixtures.LoadFixture("fixtures/segment_list.mpd")
20 | m, err := ReadFromString(xml)
21 |
22 | require.NoError(t, err)
23 | if err == nil {
24 | expected := getSegmentListMPD()
25 |
26 | require.EqualStringSlice(t, expected.Periods[0].BaseURL, m.Periods[0].BaseURL)
27 |
28 | expectedAudioSegList := expected.Periods[0].AdaptationSets[0].Representations[0].SegmentList
29 | audioSegList := m.Periods[0].AdaptationSets[0].Representations[0].SegmentList
30 |
31 | require.EqualUInt32(t, *expectedAudioSegList.Timescale, *audioSegList.Timescale)
32 | require.EqualUInt32(t, *expectedAudioSegList.Duration, *audioSegList.Duration)
33 | require.EqualStringPtr(t, expectedAudioSegList.Initialization.SourceURL, audioSegList.Initialization.SourceURL)
34 | require.EqualStringPtr(t, expectedAudioSegList.Initialization.Range, audioSegList.Initialization.Range)
35 |
36 | for i := range expectedAudioSegList.SegmentURLs {
37 | require.EqualStringPtr(t, expectedAudioSegList.SegmentURLs[i].Media, audioSegList.SegmentURLs[i].Media)
38 | require.EqualStringPtr(t, expectedAudioSegList.SegmentURLs[i].Index, audioSegList.SegmentURLs[i].Index)
39 | require.EqualStringPtr(t, expectedAudioSegList.SegmentURLs[i].IndexRange, audioSegList.SegmentURLs[i].IndexRange)
40 | require.EqualStringPtr(t, expectedAudioSegList.SegmentURLs[i].MediaRange, audioSegList.SegmentURLs[i].MediaRange)
41 | }
42 |
43 | expectedVideoSegList := expected.Periods[0].AdaptationSets[1].Representations[0].SegmentList
44 | videoSegList := m.Periods[0].AdaptationSets[1].Representations[0].SegmentList
45 |
46 | require.EqualUInt32(t, *expectedVideoSegList.Timescale, *videoSegList.Timescale)
47 | require.EqualUInt32(t, *expectedVideoSegList.Duration, *videoSegList.Duration)
48 | require.EqualStringPtr(t, expectedVideoSegList.Initialization.Range, videoSegList.Initialization.Range)
49 | require.EqualStringPtr(t, expectedVideoSegList.Initialization.SourceURL, videoSegList.Initialization.SourceURL)
50 |
51 | for i := range expectedVideoSegList.SegmentURLs {
52 | require.EqualStringPtr(t, expectedVideoSegList.SegmentURLs[i].Media, videoSegList.SegmentURLs[i].Media)
53 | require.EqualStringPtr(t, expectedVideoSegList.SegmentURLs[i].Index, videoSegList.SegmentURLs[i].Index)
54 | require.EqualStringPtr(t, expectedVideoSegList.SegmentURLs[i].IndexRange, videoSegList.SegmentURLs[i].IndexRange)
55 | require.EqualStringPtr(t, expectedVideoSegList.SegmentURLs[i].MediaRange, videoSegList.SegmentURLs[i].MediaRange)
56 | }
57 | }
58 | }
59 |
60 | func getSegmentListMPD() *MPD {
61 | m := NewMPD(DASH_PROFILE_LIVE, "PT30.016S", "PT2.000S")
62 | m.period.BaseURL = []string{"http://localhost:8002/dash/"}
63 |
64 | aas, _ := m.AddNewAdaptationSetAudioWithID("1", "audio/mp4", true, 1, "English")
65 | ra, _ := aas.AddNewRepresentationAudio(48000, 255000, "mp4a.40.2", "audio_1")
66 |
67 | asl := new(SegmentList)
68 | asl.Timescale = ptrs.Uint32ptr(48000)
69 | asl.Duration = ptrs.Uint32ptr(479232)
70 | asl.Initialization = &URL{SourceURL: ptrs.Strptr("b4324d65-ad06-4735-9535-5cd4af84ebb6/dcb11457-9092-4410-b204-67b3c6d9a9e2/init.m4f")}
71 |
72 | asegs := []*SegmentURL{}
73 | asegs = append(asegs, &SegmentURL{Media: ptrs.Strptr("b4324d65-ad06-4735-9535-5cd4af84ebb6/dcb11457-9092-4410-b204-67b3c6d9a9e2/segment0.m4f")})
74 | asegs = append(asegs, &SegmentURL{Media: ptrs.Strptr("b4324d65-ad06-4735-9535-5cd4af84ebb6/dcb11457-9092-4410-b204-67b3c6d9a9e2/segment1.m4f")})
75 | asegs = append(asegs, &SegmentURL{Media: ptrs.Strptr("b4324d65-ad06-4735-9535-5cd4af84ebb6/dcb11457-9092-4410-b204-67b3c6d9a9e2/segment2.m4f")})
76 | asegs = append(asegs, &SegmentURL{Media: ptrs.Strptr("b4324d65-ad06-4735-9535-5cd4af84ebb6/dcb11457-9092-4410-b204-67b3c6d9a9e2/segment3.m4f")})
77 | asl.SegmentURLs = asegs
78 |
79 | ra.SegmentList = asl
80 |
81 | vas, _ := m.AddNewAdaptationSetVideoWithID("2", "video/mp4", "progressive", true, 1)
82 | va, _ := vas.AddNewRepresentationVideo(int64(4172274), "avc1.640028", "video_1", "30000/1001", int64(1280), int64(720))
83 |
84 | vsl := new(SegmentList)
85 | vsl.Timescale = ptrs.Uint32ptr(30000)
86 | vsl.Duration = ptrs.Uint32ptr(225120)
87 | vsl.Initialization = &URL{SourceURL: ptrs.Strptr("b4324d65-ad06-4735-9535-5cd4af84ebb6/f2ad47b2-5362-46e6-ad1d-dff7b10f00b8/init.m4f")}
88 |
89 | vsegs := []*SegmentURL{}
90 | vsegs = append(vsegs, &SegmentURL{Media: ptrs.Strptr("b4324d65-ad06-4735-9535-5cd4af84ebb6/f2ad47b2-5362-46e6-ad1d-dff7b10f00b8/segment0.m4f")})
91 | vsegs = append(vsegs, &SegmentURL{Media: ptrs.Strptr("b4324d65-ad06-4735-9535-5cd4af84ebb6/f2ad47b2-5362-46e6-ad1d-dff7b10f00b8/segment1.m4f")})
92 | vsegs = append(vsegs, &SegmentURL{Media: ptrs.Strptr("b4324d65-ad06-4735-9535-5cd4af84ebb6/f2ad47b2-5362-46e6-ad1d-dff7b10f00b8/segment2.m4f")})
93 | vsegs = append(vsegs, &SegmentURL{Media: ptrs.Strptr("b4324d65-ad06-4735-9535-5cd4af84ebb6/f2ad47b2-5362-46e6-ad1d-dff7b10f00b8/segment3.m4f")})
94 | vsl.SegmentURLs = vsegs
95 |
96 | va.SegmentList = vsl
97 |
98 | return m
99 | }
100 |
--------------------------------------------------------------------------------
/mpd/segment_timeline_test.go:
--------------------------------------------------------------------------------
1 | package mpd
2 |
3 | import (
4 | "strconv"
5 | "testing"
6 | "time"
7 |
8 | "github.com/zencoder/go-dash/v3/helpers/ptrs"
9 | "github.com/zencoder/go-dash/v3/helpers/require"
10 | "github.com/zencoder/go-dash/v3/helpers/testfixtures"
11 | )
12 |
13 | func TestSegmentTimelineSerialization(t *testing.T) {
14 | testcases := []struct {
15 | In *MPD
16 | Out string
17 | }{
18 | {In: getSegmentTimelineMPD(), Out: "segment_timeline.mpd"},
19 | {In: getMultiPeriodSegmentTimelineMPD(), Out: "segment_timeline_multi_period.mpd"},
20 | }
21 | for _, tc := range testcases {
22 | t.Run(tc.Out, func(t *testing.T) {
23 | found, err := tc.In.WriteToString()
24 | require.NoError(t, err)
25 | testfixtures.CompareFixture(t, "fixtures/"+tc.Out, found)
26 | })
27 | }
28 | }
29 |
30 | func TestSegmentTimelineDeserialization(t *testing.T) {
31 | xml := testfixtures.LoadFixture("fixtures/segment_timeline.mpd")
32 | m, err := ReadFromString(xml)
33 | require.NoError(t, err)
34 | expected := getSegmentTimelineMPD()
35 | require.EqualStringSlice(t, expected.Periods[0].BaseURL, m.Periods[0].BaseURL)
36 |
37 | expectedAudioSegTimeline := expected.Periods[0].AdaptationSets[0].Representations[0].SegmentTemplate.SegmentTimeline
38 | audioSegTimeline := m.Periods[0].AdaptationSets[0].Representations[0].SegmentTemplate.SegmentTimeline
39 |
40 | for i := range expectedAudioSegTimeline.Segments {
41 | require.EqualUInt64(t, expectedAudioSegTimeline.Segments[i].Duration, audioSegTimeline.Segments[i].Duration)
42 | require.EqualUInt64Ptr(t, expectedAudioSegTimeline.Segments[i].StartTime, audioSegTimeline.Segments[i].StartTime)
43 | require.EqualIntPtr(t, expectedAudioSegTimeline.Segments[i].RepeatCount, audioSegTimeline.Segments[i].RepeatCount)
44 | }
45 |
46 | expectedVideoSegTimeline := expected.Periods[0].AdaptationSets[1].Representations[0].SegmentTemplate.SegmentTimeline
47 | videoSegTimeline := m.Periods[0].AdaptationSets[1].Representations[0].SegmentTemplate.SegmentTimeline
48 |
49 | for i := range expectedVideoSegTimeline.Segments {
50 | require.EqualUInt64(t, expectedVideoSegTimeline.Segments[i].Duration, videoSegTimeline.Segments[i].Duration)
51 | require.EqualUInt64Ptr(t, expectedVideoSegTimeline.Segments[i].StartTime, videoSegTimeline.Segments[i].StartTime)
52 | require.EqualIntPtr(t, expectedVideoSegTimeline.Segments[i].RepeatCount, videoSegTimeline.Segments[i].RepeatCount)
53 | }
54 | }
55 |
56 | func getMultiPeriodSegmentTimelineMPD() *MPD {
57 | m := NewMPD(DASH_PROFILE_LIVE, "PT65.063S", "PT2.000S")
58 | for i := 0; i < 4; i++ {
59 | if i > 0 {
60 | m.AddNewPeriod()
61 | }
62 | p := m.GetCurrentPeriod()
63 | p.ID = strconv.Itoa(i)
64 | p.Duration = Duration(30 * time.Second)
65 | aas, _ := p.AddNewAdaptationSetAudioWithID("1", "audio/mp4", true, 1, "en")
66 | _, _ = aas.AddNewRepresentationAudio(48000, 92000, "mp4a.40.2", "audio_1")
67 | aas.SegmentTemplate = &SegmentTemplate{
68 | Timescale: ptrs.Int64ptr(48000),
69 | Initialization: ptrs.Strptr("audio/init.m4f"),
70 | Media: ptrs.Strptr("audio/segment$Number$.m4f"),
71 | SegmentTimeline: &SegmentTimeline{
72 | Segments: []*SegmentTimelineSegment{
73 | {Duration: 95232, RepeatCount: ptrs.Intptr(14)},
74 | {Duration: 15360},
75 | },
76 | },
77 | }
78 | vas, _ := p.AddNewAdaptationSetVideoWithID("2", "video/mp4", "progressive", true, 1)
79 | _, _ = vas.AddNewRepresentationVideo(3532000, "avc1.640028", "video_1", "2997/100", 2048, 854)
80 | _, _ = vas.AddNewRepresentationVideo(453000, "avc1.420016", "video_2", "2997/100", 648, 270)
81 | vas.SegmentTemplate = &SegmentTemplate{
82 | Timescale: ptrs.Int64ptr(30000),
83 | Initialization: ptrs.Strptr("video/$RepresentationID$/init.m4f"),
84 | Media: ptrs.Strptr("video/$RepresentationID$/segment$Number$.m4f"),
85 | SegmentTimeline: &SegmentTimeline{
86 | Segments: []*SegmentTimelineSegment{
87 | {Duration: 58058, RepeatCount: ptrs.Intptr(14)},
88 | {Duration: 31031},
89 | },
90 | },
91 | }
92 | // Add Special flags on 3rd Period, to simulate cutting a piece of content midway
93 | if i == 2 {
94 | aas.SegmentTemplate.StartNumber = ptrs.Int64ptr(17)
95 | aas.SegmentTemplate.PresentationTimeOffset = ptrs.Uint64ptr(743424)
96 | vas.SegmentTemplate.StartNumber = ptrs.Int64ptr(17)
97 | vas.SegmentTemplate.PresentationTimeOffset = ptrs.Uint64ptr(464464)
98 | }
99 | }
100 | return m
101 | }
102 |
103 | func getSegmentTimelineMPD() *MPD {
104 | m := NewMPD(DASH_PROFILE_LIVE, "PT65.063S", "PT2.000S")
105 | m.period.BaseURL = []string{"http://localhost:8002/public/"}
106 |
107 | aas, _ := m.AddNewAdaptationSetAudioWithID("1", "audio/mp4", true, 1, "English")
108 | ra, _ := aas.AddNewRepresentationAudio(48000, 255000, "mp4a.40.2", "audio_1")
109 |
110 | ra.SegmentTemplate = &SegmentTemplate{
111 | Timescale: ptrs.Int64ptr(48000),
112 | Initialization: ptrs.Strptr("audio/init.m4f"),
113 | Media: ptrs.Strptr("audio/segment$Number$.m4f"),
114 | SegmentTimeline: &SegmentTimeline{
115 | Segments: []*SegmentTimelineSegment{
116 | {StartTime: ptrs.Uint64ptr(0), Duration: 231424},
117 | {RepeatCount: ptrs.Intptr(2), Duration: 479232},
118 | {Duration: 10240},
119 | {Duration: 247808},
120 | {RepeatCount: ptrs.Intptr(1), Duration: 479232},
121 | {Duration: 3072},
122 | },
123 | },
124 | }
125 |
126 | vas, _ := m.AddNewAdaptationSetVideoWithID("2", "video/mp4", "progressive", true, 1)
127 | va, _ := vas.AddNewRepresentationVideo(int64(4172274), "avc1.640028", "video_1", "30000/1001", int64(1280), int64(720))
128 |
129 | va.SegmentTemplate = &SegmentTemplate{
130 | Timescale: ptrs.Int64ptr(30000),
131 | Initialization: ptrs.Strptr("video/init.m4f"),
132 | Media: ptrs.Strptr("video/segment$Number$.m4f"),
133 | SegmentTimeline: &SegmentTimeline{
134 | Segments: []*SegmentTimelineSegment{
135 | {StartTime: ptrs.Uint64ptr(0), Duration: 145145},
136 | {RepeatCount: ptrs.Intptr(2), Duration: 270270},
137 | {Duration: 91091},
138 | {Duration: 125125},
139 | {RepeatCount: ptrs.Intptr(1), Duration: 270270},
140 | {Duration: 88088},
141 | },
142 | },
143 | }
144 | return m
145 | }
146 |
--------------------------------------------------------------------------------
/mpd/validate.go:
--------------------------------------------------------------------------------
1 | package mpd
2 |
3 | // Validate checks for incomplete MPD object
4 | func (m *MPD) Validate() error {
5 | if m.Profiles == nil {
6 | return ErrNoDASHProfileSet
7 | }
8 | return nil
9 | }
10 |
--------------------------------------------------------------------------------