Skip to content

Commit ad5e45c

Browse files
author
Zac Shenker
committed
Adding support for ContentProtection for Widevine & Playready
1 parent 335b99c commit ad5e45c

File tree

2 files changed

+171
-8
lines changed

2 files changed

+171
-8
lines changed

mpd/mpd.go

Lines changed: 125 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package mpd
22

33
import (
44
"errors"
5+
"strings"
56

67
. "github.com/zencoder/go-dash/helpers/ptrs"
78
)
@@ -23,6 +24,9 @@ var (
2324
ErrBaseURLEmpty = errors.New("Base URL empty")
2425
ErrSegmentBaseOnDemandProfileOnly = errors.New("Segment Base can only be used with On-Demand Profile")
2526
ErrSegmentBaseNil = errors.New("Segment Base nil")
27+
ErrInvalidDefaultKID = errors.New("Invalid Default KID string, should be 32 characters")
28+
ErrPROEmpty = errors.New("PlayReady PRO empty")
29+
ErrContentProtectionNil = errors.New("Content Protection nil")
2630
)
2731

2832
type MPD struct {
@@ -39,14 +43,40 @@ type Period struct {
3943
}
4044

4145
type AdaptationSet struct {
42-
MPD *MPD `xml:"-"`
43-
MimeType *string `xml:"mimeType,attr"`
44-
ScanType *string `xml:"scanType,attr"`
45-
SegmentAlignment *bool `xml:"segmentAlignment,attr"`
46-
StartWithSAP *int64 `xml:"startWithSAP,attr"`
47-
Lang *string `xml:"lang,attr"`
48-
SegmentTemplate *SegmentTemplate `xml:"SegmentTemplate,omitempty"` // Live Profile Only
49-
Representations []*Representation `xml:"Representation,omitempty"`
46+
MPD *MPD `xml:"-"`
47+
MimeType *string `xml:"mimeType,attr"`
48+
ScanType *string `xml:"scanType,attr"`
49+
SegmentAlignment *bool `xml:"segmentAlignment,attr"`
50+
StartWithSAP *int64 `xml:"startWithSAP,attr"`
51+
Lang *string `xml:"lang,attr"`
52+
ContentProtection []*ContentProtection `xml:"ContentProtection,omitempty"`
53+
SegmentTemplate *SegmentTemplate `xml:"SegmentTemplate,omitempty"` // Live Profile Only
54+
Representations []*Representation `xml:"Representation,omitempty"`
55+
}
56+
57+
/*
58+
<ContentProtection cenc:default_KID="09e36702-8f33-436c-a5dd60ffe6671e70" schemeIdUri="urn:mpeg:dash:mp4protection:2011" value="cenc" xmlns:cenc="urn:mpeg:cenc:2013"/>
59+
<ContentProtection schemeIdUri="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"/>
60+
<ContentProtection schemeIdUri="urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95">
61+
<mspr:pro>mgIAAAEAAQCQAjwAVwBSAE0ASABFAEEARABFAFIAIAB4AG0AbABuAHMAPQAiAGgAdAB0AHAAOgAvAC8AcwBjAGgAZQBtAGEAcwAuAG0AaQBjAHIAbwBzAG8AZgB0AC4AYwBvAG0ALwBEAFIATQAvADIAMAAwADcALwAwADMALwBQAGwAYQB5AFIAZQBhAGQAeQBIAGUAYQBkAGUAcgAiACAAdgBlAHIAcwBpAG8AbgA9ACIANAAuADAALgAwAC4AMAAiAD4APABEAEEAVABBAD4APABQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsARQBZAEwARQBOAD4AMQA2ADwALwBLAEUAWQBMAEUATgA+ADwAQQBMAEcASQBEAD4AQQBFAFMAQwBUAFIAPAAvAEEATABHAEkARAA+ADwALwBQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsASQBEAD4AQQBtAGYAagBDAFQATwBQAGIARQBPAGwAMwBXAEQALwA1AG0AYwBlAGMAQQA9AD0APAAvAEsASQBEAD4APABDAEgARQBDAEsAUwBVAE0APgBCAEcAdwAxAGEAWQBaADEAWQBYAE0APQA8AC8AQwBIAEUAQwBLAFMAVQBNAD4APABMAEEAXwBVAFIATAA+AGgAdAB0AHAAOgAvAC8AcABsAGEAeQByAGUAYQBkAHkALgBkAGkAcgBlAGMAdAB0AGEAcABzAC4AbgBlAHQALwBwAHIALwBzAHYAYwAvAHIAaQBnAGgAdABzAG0AYQBuAGEAZwBlAHIALgBhAHMAbQB4ADwALwBMAEEAXwBVAFIATAA+ADwALwBEAEEAVABBAD4APAAvAFcAUgBNAEgARQBBAEQARQBSAD4A</mspr:pro>
62+
</ContentProtection>
63+
*/
64+
65+
const (
66+
CONTENT_PROTECTION_ROOT_SCHEME_ID_URI = "urn:mpeg:dash:mp4protection:2011"
67+
CONTENT_PROTECTION_ROOT_VALUE = "cenc"
68+
CONTENT_PROTECTION_ROOT_XMLNS = "urn:mpeg:cenc:2013"
69+
CONTENT_PROTECTION_WIDEVINE_SCHEME_ID = "urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"
70+
CONTENT_PROTECTION_PLAYREADY_SCHEME_ID = "urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95"
71+
)
72+
73+
type ContentProtection struct {
74+
AdaptationSet *AdaptationSet `xml:"-"`
75+
DefaultKID *string `xml:"cenc:default_KID,attr"`
76+
SchemeIDURI *string `xml:"schemeIdUri,attr"` // Default: urn:mpeg:dash:mp4protection:2011
77+
Value *string `xml:"value,attr"` // Default: cenc
78+
XMLNS *string `xml:"xmlns:cenc,attr"` // Default: urn:mpeg:cenc:2013
79+
PlayreadyPRO *string `xml:"mspr:pro,omitempty"`
5080
}
5181

5282
// Live Profile Only
@@ -131,6 +161,93 @@ func (m *MPD) AddAdaptationSet(as *AdaptationSet) error {
131161
return nil
132162
}
133163

164+
/*
165+
<ContentProtection cenc:default_KID="09e36702-8f33-436c-a5dd60ffe6671e70" schemeIdUri="urn:mpeg:dash:mp4protection:2011" value="cenc" xmlns:cenc="urn:mpeg:cenc:2013"/>
166+
<ContentProtection schemeIdUri="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"/>
167+
<ContentProtection schemeIdUri="urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95">
168+
<mspr:pro>mgIAAAEAAQCQAjwAVwBSAE0ASABFAEEARABFAFIAIAB4AG0AbABuAHMAPQAiAGgAdAB0AHAAOgAvAC8AcwBjAGgAZQBtAGEAcwAuAG0AaQBjAHIAbwBzAG8AZgB0AC4AYwBvAG0ALwBEAFIATQAvADIAMAAwADcALwAwADMALwBQAGwAYQB5AFIAZQBhAGQAeQBIAGUAYQBkAGUAcgAiACAAdgBlAHIAcwBpAG8AbgA9ACIANAAuADAALgAwAC4AMAAiAD4APABEAEEAVABBAD4APABQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsARQBZAEwARQBOAD4AMQA2ADwALwBLAEUAWQBMAEUATgA+ADwAQQBMAEcASQBEAD4AQQBFAFMAQwBUAFIAPAAvAEEATABHAEkARAA+ADwALwBQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsASQBEAD4AQQBtAGYAagBDAFQATwBQAGIARQBPAGwAMwBXAEQALwA1AG0AYwBlAGMAQQA9AD0APAAvAEsASQBEAD4APABDAEgARQBDAEsAUwBVAE0APgBCAEcAdwAxAGEAWQBaADEAWQBYAE0APQA8AC8AQwBIAEUAQwBLAFMAVQBNAD4APABMAEEAXwBVAFIATAA+AGgAdAB0AHAAOgAvAC8AcABsAGEAeQByAGUAYQBkAHkALgBkAGkAcgBlAGMAdAB0AGEAcABzAC4AbgBlAHQALwBwAHIALwBzAHYAYwAvAHIAaQBnAGgAdABzAG0AYQBuAGEAZwBlAHIALgBhAHMAbQB4ADwALwBMAEEAXwBVAFIATAA+ADwALwBEAEEAVABBAD4APAAvAFcAUgBNAEgARQBBAEQARQBSAD4A</mspr:pro>
169+
</ContentProtection>
170+
171+
172+
type ContentProtection struct {
173+
AdaptationSet *AdaptationSet `xml:"-"`
174+
DefaultKID *string `xml:"cenc:default_KID,attr"`
175+
SchemeIDURI *string `xml:"schemeIdUri,attr"` // Default: urn:mpeg:dash:mp4protection:2011
176+
Value *string `xml:"value,attr"` // Default: cenc
177+
XMLNS *string `xml:"xmlns:cenc,attr"` // Default: urn:mpeg:cenc:2013
178+
PlayReadyPRO *string `xml:"mspr:pro,omitempty"`
179+
}
180+
181+
const (
182+
CONTENT_PROTECTION_ROOT_SCHEME_ID_URI = "urn:mpeg:dash:mp4protection:2011"
183+
CONTENT_PROTECTION_ROOT_VALUE = "cenc"
184+
CONTENT_PROTECTION_ROOT_XMLNS = "urn:mpeg:cenc:2013"
185+
)
186+
*/
187+
188+
func (as *AdaptationSet) AddNewContentProtectionRoot(defaultKIDHex string) (*ContentProtection, error) {
189+
if len(defaultKIDHex) != 32 || defaultKIDHex == "" {
190+
return nil, ErrInvalidDefaultKID
191+
}
192+
193+
// Convert the KID into the correct format
194+
defaultKID := strings.ToLower(defaultKIDHex[0:8] + "-" + defaultKIDHex[8:12] + "-" + defaultKIDHex[12:16] + "-" + defaultKIDHex[16:32])
195+
196+
cp := &ContentProtection{
197+
DefaultKID: Strptr(defaultKID),
198+
SchemeIDURI: Strptr(CONTENT_PROTECTION_ROOT_SCHEME_ID_URI),
199+
Value: Strptr(CONTENT_PROTECTION_ROOT_VALUE),
200+
XMLNS: Strptr(CONTENT_PROTECTION_ROOT_XMLNS),
201+
}
202+
203+
err := as.AddContentProtection(cp)
204+
if err != nil {
205+
return nil, err
206+
}
207+
208+
return cp, nil
209+
}
210+
211+
func (as *AdaptationSet) AddNewContentProtectionSchemeWidevine() (*ContentProtection, error) {
212+
cp := &ContentProtection{
213+
SchemeIDURI: Strptr(CONTENT_PROTECTION_WIDEVINE_SCHEME_ID),
214+
}
215+
216+
err := as.AddContentProtection(cp)
217+
if err != nil {
218+
return nil, err
219+
}
220+
221+
return cp, nil
222+
}
223+
224+
func (as *AdaptationSet) AddNewContentProtectionSchemePlayready(pro string) (*ContentProtection, error) {
225+
if pro == "" {
226+
return nil, ErrPROEmpty
227+
}
228+
229+
cp := &ContentProtection{
230+
SchemeIDURI: Strptr(CONTENT_PROTECTION_PLAYREADY_SCHEME_ID),
231+
PlayreadyPRO: Strptr(pro),
232+
}
233+
234+
err := as.AddContentProtection(cp)
235+
if err != nil {
236+
return nil, err
237+
}
238+
239+
return cp, nil
240+
}
241+
242+
func (as *AdaptationSet) AddContentProtection(cp *ContentProtection) error {
243+
if cp == nil {
244+
return ErrContentProtectionNil
245+
}
246+
247+
as.ContentProtection = append(as.ContentProtection, cp)
248+
return nil
249+
}
250+
134251
func (as *AdaptationSet) SetNewSegmentTemplate(duration int64, init string, media string, startNumber int64, timescale int64) (*SegmentTemplate, error) {
135252
st := &SegmentTemplate{
136253
Duration: Intptr(duration),

mpd/mpd_test.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ const (
5151
VALID_BASE_URL_VIDEO string = "800k/output-video-1.mp4"
5252
VALID_INDEX_RANGE string = "629-756"
5353
VALID_INIT_RANGE string = "0-628"
54+
VALID_DEFAULT_KID_HEX string = "09e367028f33436ca5dd60ffe6671e70"
55+
VALID_DEFAULT_KID string = "09e36702-8f33-436c-a5dd60ffe6671e70"
56+
VALID_PLAYREADY_PRO string = "mgIAAAEAAQCQAjwAVwBSAE0ASABFAEEARABFAFIAIAB4AG0AbABuAHMAPQAiAGgAdAB0AHAAOgAvAC8AcwBjAGgAZQBtAGEAcwAuAG0AaQBjAHIAbwBzAG8AZgB0AC4AYwBvAG0ALwBEAFIATQAvADIAMAAwADcALwAwADMALwBQAGwAYQB5AFIAZQBhAGQAeQBIAGUAYQBkAGUAcgAiACAAdgBlAHIAcwBpAG8AbgA9ACIANAAuADAALgAwAC4AMAAiAD4APABEAEEAVABBAD4APABQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsARQBZAEwARQBOAD4AMQA2ADwALwBLAEUAWQBMAEUATgA+ADwAQQBMAEcASQBEAD4AQQBFAFMAQwBUAFIAPAAvAEEATABHAEkARAA+ADwALwBQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsASQBEAD4AQQBtAGYAagBDAFQATwBQAGIARQBPAGwAMwBXAEQALwA1AG0AYwBlAGMAQQA9AD0APAAvAEsASQBEAD4APABDAEgARQBDAEsAUwBVAE0APgBCAEcAdwAxAGEAWQBaADEAWQBYAE0APQA8AC8AQwBIAEUAQwBLAFMAVQBNAD4APABMAEEAXwBVAFIATAA+AGgAdAB0AHAAOgAvAC8AcABsAGEAeQByAGUAYQBkAHkALgBkAGkAcgBlAGMAdAB0AGEAcABzAC4AbgBlAHQALwBwAHIALwBzAHYAYwAvAHIAaQBnAGgAdABzAG0AYQBuAGEAZwBlAHIALgBhAHMAbQB4ADwALwBMAEEAXwBVAFIATAA+ADwALwBEAEEAVABBAD4APAAvAFcAUgBNAEgARQBBAEQARQBSAD4A"
5457
)
5558

5659
func (s *MPDSuite) TestNewMPDLive() {
@@ -120,6 +123,49 @@ func (s *MPDSuite) TestAddAdaptationSetErrorNil() {
120123
assert.Equal(s.T(), ErrAdaptationSetNil, err)
121124
}
122125

126+
func (s *MPDSuite) TestAddNewContentProtectionRoot() {
127+
m := NewMPD(DASH_PROFILE_LIVE, VALID_MEDIA_PRESENTATION_DURATION, VALID_MIN_BUFFER_TIME)
128+
as, _ := m.AddNewAdaptationSetVideo(VALID_SCAN_TYPE, VALID_SEGMENT_ALIGNMENT, VALID_START_WITH_SAP)
129+
130+
cp, err := as.AddNewContentProtectionRoot(VALID_DEFAULT_KID_HEX)
131+
assert.Nil(s.T(), err)
132+
assert.NotNil(s.T(), cp)
133+
expectedCP := &ContentProtection{
134+
DefaultKID: Strptr(VALID_DEFAULT_KID),
135+
SchemeIDURI: Strptr(CONTENT_PROTECTION_ROOT_SCHEME_ID_URI),
136+
Value: Strptr(CONTENT_PROTECTION_ROOT_VALUE),
137+
XMLNS: Strptr(CONTENT_PROTECTION_ROOT_XMLNS),
138+
}
139+
assert.Equal(s.T(), expectedCP, cp)
140+
}
141+
142+
func (s *MPDSuite) TestAddNewContentProtectionSchemeWidevine() {
143+
m := NewMPD(DASH_PROFILE_LIVE, VALID_MEDIA_PRESENTATION_DURATION, VALID_MIN_BUFFER_TIME)
144+
as, _ := m.AddNewAdaptationSetVideo(VALID_SCAN_TYPE, VALID_SEGMENT_ALIGNMENT, VALID_START_WITH_SAP)
145+
146+
cp, err := as.AddNewContentProtectionSchemeWidevine()
147+
assert.Nil(s.T(), err)
148+
assert.NotNil(s.T(), cp)
149+
expectedCP := &ContentProtection{
150+
SchemeIDURI: Strptr(CONTENT_PROTECTION_WIDEVINE_SCHEME_ID),
151+
}
152+
assert.Equal(s.T(), expectedCP, cp)
153+
}
154+
155+
func (s *MPDSuite) TestAddNewContentProtectionSchemePlayready() {
156+
m := NewMPD(DASH_PROFILE_LIVE, VALID_MEDIA_PRESENTATION_DURATION, VALID_MIN_BUFFER_TIME)
157+
as, _ := m.AddNewAdaptationSetVideo(VALID_SCAN_TYPE, VALID_SEGMENT_ALIGNMENT, VALID_START_WITH_SAP)
158+
159+
cp, err := as.AddNewContentProtectionSchemePlayready(VALID_PLAYREADY_PRO)
160+
assert.Nil(s.T(), err)
161+
assert.NotNil(s.T(), cp)
162+
expectedCP := &ContentProtection{
163+
SchemeIDURI: Strptr(CONTENT_PROTECTION_PLAYREADY_SCHEME_ID),
164+
PlayreadyPRO: Strptr(VALID_PLAYREADY_PRO),
165+
}
166+
assert.Equal(s.T(), expectedCP, cp)
167+
}
168+
123169
func (s *MPDSuite) TestSetNewSegmentTemplate() {
124170
m := NewMPD(DASH_PROFILE_LIVE, VALID_MEDIA_PRESENTATION_DURATION, VALID_MIN_BUFFER_TIME)
125171
audioAS, _ := m.AddNewAdaptationSetAudio(VALID_SEGMENT_ALIGNMENT, VALID_START_WITH_SAP, VALID_LANG)

0 commit comments

Comments
 (0)