diff --git a/mpd/mpd.go b/mpd/mpd.go index 14bc473..e2e41a1 100644 --- a/mpd/mpd.go +++ b/mpd/mpd.go @@ -93,14 +93,16 @@ type AdaptationSet struct { // Constants for DRM / ContentProtection const ( - CONTENT_PROTECTION_ROOT_SCHEME_ID_URI = "urn:mpeg:dash:mp4protection:2011" - CONTENT_PROTECTION_ROOT_VALUE = "cenc" - CENC_XMLNS = "urn:mpeg:cenc:2013" - CONTENT_PROTECTION_WIDEVINE_SCHEME_ID = "urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed" - CONTENT_PROTECTION_WIDEVINE_SCHEME_HEX = "edef8ba979d64acea3c827dcd51d21ed" - CONTENT_PROTECTION_PLAYREADY_SCHEME_ID = "urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95" - CONTENT_PROTECTION_PLAYREADY_SCHEME_HEX = "9a04f07998404286ab92e65be0885f95" - CONTENT_PROTECTION_PLAYREADY_XMLNS = "urn:microsoft:playready" + CONTENT_PROTECTION_ROOT_SCHEME_ID_URI = "urn:mpeg:dash:mp4protection:2011" + CONTENT_PROTECTION_ROOT_VALUE = "cenc" + CENC_XMLNS = "urn:mpeg:cenc:2013" + CONTENT_PROTECTION_WIDEVINE_SCHEME_ID = "urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed" + CONTENT_PROTECTION_WIDEVINE_SCHEME_HEX = "edef8ba979d64acea3c827dcd51d21ed" + CONTENT_PROTECTION_PLAYREADY_SCHEME_ID = "urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95" + CONTENT_PROTECTION_PLAYREADY_SCHEME_HEX = "9a04f07998404286ab92e65be0885f95" + CONTENT_PROTECTION_PLAYREADY_SCHEME_V10_ID = "urn:uuid:79f0049a-4098-8642-ab92-e65be0885f95" + CONTENT_PROTECTION_PLAYREADY_SCHEME_V10_HEX = "79f0049a40988642ab92e65be0885f95" + CONTENT_PROTECTION_PLAYREADY_XMLNS = "urn:microsoft:playready" ) type ContentProtectioner interface { @@ -332,7 +334,7 @@ func NewWidevineContentProtection(wvHeader []byte) (*WidevineContentProtection, // AddNewContentProtectionSchemePlayready adds a new content protection scheme for PlayReady DRM. // pro - PlayReady Object Header, as a Base64 encoded string. func (as *AdaptationSet) AddNewContentProtectionSchemePlayready(pro string) (*PlayreadyContentProtection, error) { - cp, err := newPlayreadyContentProtection(pro) + cp, err := newPlayreadyContentProtection(pro, CONTENT_PROTECTION_PLAYREADY_SCHEME_ID) if err != nil { return nil, err } @@ -344,7 +346,22 @@ func (as *AdaptationSet) AddNewContentProtectionSchemePlayready(pro string) (*Pl return cp, nil } -func newPlayreadyContentProtection(pro string) (*PlayreadyContentProtection, error) { +// AddNewContentProtectionSchemePlayreadyV10 adds a new content protection scheme for PlayReady v1.0 DRM. +// pro - PlayReady Object Header, as a Base64 encoded string. +func (as *AdaptationSet) AddNewContentProtectionSchemePlayreadyV10(pro string) (*PlayreadyContentProtection, error) { + cp, err := newPlayreadyContentProtection(pro, CONTENT_PROTECTION_PLAYREADY_SCHEME_V10_ID) + if err != nil { + return nil, err + } + + err = as.AddContentProtection(cp) + if err != nil { + return nil, err + } + return cp, nil +} + +func newPlayreadyContentProtection(pro string, schemeIDURI string) (*PlayreadyContentProtection, error) { if pro == "" { return nil, ErrPROEmpty } @@ -353,7 +370,7 @@ func newPlayreadyContentProtection(pro string) (*PlayreadyContentProtection, err PlayreadyXMLNS: Strptr(CONTENT_PROTECTION_PLAYREADY_XMLNS), PRO: Strptr(pro), } - cp.SchemeIDURI = Strptr(CONTENT_PROTECTION_PLAYREADY_SCHEME_ID) + cp.SchemeIDURI = Strptr(schemeIDURI) return cp, nil } @@ -362,7 +379,7 @@ func newPlayreadyContentProtection(pro string) (*PlayreadyContentProtection, err // will include both ms:pro and cenc:pssh subelements // pro - PlayReady Object Header, as a Base64 encoded string. func (as *AdaptationSet) AddNewContentProtectionSchemePlayreadyWithPSSH(pro string) (*PlayreadyContentProtection, error) { - cp, err := newPlayreadyContentProtection(pro) + cp, err := newPlayreadyContentProtection(pro, CONTENT_PROTECTION_PLAYREADY_SCHEME_ID) if err != nil { return nil, err } @@ -390,6 +407,38 @@ func (as *AdaptationSet) AddNewContentProtectionSchemePlayreadyWithPSSH(pro stri return cp, nil } +// AddNewContentProtectionSchemePlayreadyV10WithPSSH adds a new content protection scheme for PlayReady v1.0 DRM. The scheme +// will include both ms:pro and cenc:pssh subelements +// pro - PlayReady Object Header, as a Base64 encoded string. +func (as *AdaptationSet) AddNewContentProtectionSchemePlayreadyV10WithPSSH(pro string) (*PlayreadyContentProtection, error) { + cp, err := newPlayreadyContentProtection(pro, CONTENT_PROTECTION_PLAYREADY_SCHEME_V10_ID) + if err != nil { + return nil, err + } + cp.XMLNS = Strptr(CENC_XMLNS) + prSystemID, err := hex.DecodeString(CONTENT_PROTECTION_PLAYREADY_SCHEME_V10_HEX) + if err != nil { + panic(err.Error()) + } + + proBin, err := base64.StdEncoding.DecodeString(pro) + if err != nil { + return nil, err + } + + psshBox, err := makePSSHBox(prSystemID, proBin) + if err != nil { + return nil, err + } + cp.PSSH = Strptr(base64.StdEncoding.EncodeToString(psshBox)) + + err = as.AddContentProtection(cp) + if err != nil { + return nil, err + } + return cp, nil +} + // Internal helper method for adding a ContentProtection to an AdaptationSet. func (as *AdaptationSet) AddContentProtection(cp ContentProtectioner) error { if cp == nil { diff --git a/mpd/mpd_test.go b/mpd/mpd_test.go index 764ac8b..08b5033 100644 --- a/mpd/mpd_test.go +++ b/mpd/mpd_test.go @@ -330,6 +330,32 @@ func (s *MPDSuite) TestAddNewContentProtectionSchemePlayready() { assert.Equal(s.T(), expectedCP, cp) } +func (s *MPDSuite) TestAddNewContentProtectionSchemePlayreadyV10ErrorEmptyPRO() { + m := NewMPD(DASH_PROFILE_LIVE, VALID_MEDIA_PRESENTATION_DURATION, VALID_MIN_BUFFER_TIME) + as, _ := m.AddNewAdaptationSetVideo(DASH_MIME_TYPE_VIDEO_MP4, VALID_SCAN_TYPE, VALID_SEGMENT_ALIGNMENT, VALID_START_WITH_SAP) + + cp, err := as.AddNewContentProtectionSchemePlayreadyV10("") + assert.NotNil(s.T(), err) + assert.Equal(s.T(), ErrPROEmpty, err) + assert.Nil(s.T(), cp) +} + +func (s *MPDSuite) TestAddNewContentProtectionSchemePlayreadyV10() { + m := NewMPD(DASH_PROFILE_LIVE, VALID_MEDIA_PRESENTATION_DURATION, VALID_MIN_BUFFER_TIME) + as, _ := m.AddNewAdaptationSetVideo(DASH_MIME_TYPE_VIDEO_MP4, VALID_SCAN_TYPE, VALID_SEGMENT_ALIGNMENT, VALID_START_WITH_SAP) + + cp, err := as.AddNewContentProtectionSchemePlayreadyV10(VALID_PLAYREADY_PRO) + assert.Nil(s.T(), err) + assert.NotNil(s.T(), cp) + expectedCP := &PlayreadyContentProtection{ + PlayreadyXMLNS: Strptr(VALID_PLAYREADY_XMLNS), + PRO: Strptr(VALID_PLAYREADY_PRO), + } + expectedCP.SchemeIDURI = Strptr(CONTENT_PROTECTION_PLAYREADY_SCHEME_V10_ID) + + assert.Equal(s.T(), expectedCP, cp) +} + func (s *MPDSuite) TestAddNewContentProtectionSchemePlayreadyWithPSSH() { m := NewMPD(DASH_PROFILE_LIVE, VALID_MEDIA_PRESENTATION_DURATION, VALID_MIN_BUFFER_TIME) as, _ := m.AddNewAdaptationSetVideo(DASH_MIME_TYPE_VIDEO_MP4, VALID_SCAN_TYPE, VALID_SEGMENT_ALIGNMENT, VALID_START_WITH_SAP) @@ -348,6 +374,24 @@ func (s *MPDSuite) TestAddNewContentProtectionSchemePlayreadyWithPSSH() { assert.Equal(s.T(), expectedCP, cp) } +func (s *MPDSuite) TestAddNewContentProtectionSchemePlayreadyV10WithPSSH() { + m := NewMPD(DASH_PROFILE_LIVE, VALID_MEDIA_PRESENTATION_DURATION, VALID_MIN_BUFFER_TIME) + as, _ := m.AddNewAdaptationSetVideo(DASH_MIME_TYPE_VIDEO_MP4, VALID_SCAN_TYPE, VALID_SEGMENT_ALIGNMENT, VALID_START_WITH_SAP) + + cp, err := as.AddNewContentProtectionSchemePlayreadyV10WithPSSH(VALID_PLAYREADY_PRO) + assert.Nil(s.T(), err) + assert.NotNil(s.T(), cp) + expectedCP := &PlayreadyContentProtection{ + PlayreadyXMLNS: Strptr(VALID_PLAYREADY_XMLNS), + PRO: Strptr(VALID_PLAYREADY_PRO), + } + expectedCP.SchemeIDURI = Strptr(CONTENT_PROTECTION_PLAYREADY_SCHEME_V10_ID) + expectedCP.XMLNS = Strptr(CENC_XMLNS) + expectedCP.PSSH = Strptr("AAACJnBzc2gAAAAAefAEmkCYhkKrkuZb4IhflQAAAgYGAgAAAQABAPwBPABXAFIATQBIAEUAQQBEAEUAUgAgAHgAbQBsAG4AcwA9ACIAaAB0AHQAcAA6AC8ALwBzAGMAaABlAG0AYQBzAC4AbQBpAGMAcgBvAHMAbwBmAHQALgBjAG8AbQAvAEQAUgBNAC8AMgAwADAANwAvADAAMwAvAFAAbABhAHkAUgBlAGEAZAB5AEgAZQBhAGQAZQByACIAIAB2AGUAcgBzAGkAbwBuAD0AIgA0AC4AMAAuADAALgAwACIAPgA8AEQAQQBUAEEAPgA8AFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBFAFkATABFAE4APgAxADYAPAAvAEsARQBZAEwARQBOAD4APABBAEwARwBJAEQAPgBBAEUAUwBDAFQAUgA8AC8AQQBMAEcASQBEAD4APAAvAFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBJAEQAPgBMADkAVwA5AFcAawBwAFYASwBrACsANAAwAEcASAAzAFkAVQBKAFIAVgBRAD0APQA8AC8ASwBJAEQAPgA8AEMASABFAEMASwBTAFUATQA+AEkASwB6AFkAMgBIAFoATABBAGwASQA9ADwALwBDAEgARQBDAEsAUwBVAE0APgA8AC8ARABBAFQAQQA+ADwALwBXAFIATQBIAEUAQQBEAEUAUgA+AA==") + + assert.Equal(s.T(), expectedCP, cp) +} + func (s *MPDSuite) TestSetNewSegmentTemplate() { m := NewMPD(DASH_PROFILE_LIVE, VALID_MEDIA_PRESENTATION_DURATION, VALID_MIN_BUFFER_TIME) audioAS, _ := m.AddNewAdaptationSetAudio(DASH_MIME_TYPE_AUDIO_MP4, VALID_SEGMENT_ALIGNMENT, VALID_START_WITH_SAP, VALID_LANG)