@@ -859,132 +859,124 @@ export const setupNativeFairplayDRM = (
859859 props : Partial < Pick < MuxMediaPropsInternal , 'playbackId' | 'tokens' | 'playbackToken' | 'customDomain' | 'drmTypeCb' > > ,
860860 mediaEl : HTMLMediaElement
861861) => {
862- const onFpEncrypted = async ( event : MediaEncryptedEvent ) => {
863- try {
864- const initDataType = event . initDataType ;
865- if ( initDataType !== 'skd' ) {
866- console . error ( `Received unexpected initialization data type "${ initDataType } "` ) ;
867- return ;
868- }
869-
870- if ( ! mediaEl . mediaKeys ) {
871- const access = await navigator
872- . requestMediaKeySystemAccess ( 'com.apple.fps' , [
873- {
874- initDataTypes : [ initDataType ] ,
875- videoCapabilities : [ { contentType : 'application/vnd.apple.mpegurl' , robustness : '' } ] ,
876- distinctiveIdentifier : 'not-allowed' ,
877- persistentState : 'not-allowed' ,
878- sessionTypes : [ 'temporary' ] ,
879- } ,
880- ] )
881- . then ( ( value ) => {
882- props . drmTypeCb ?.( DRMType . FAIRPLAY ) ;
883- return value ;
884- } )
885- . catch ( ( ) => {
886- const message = i18n (
887- 'Cannot play DRM-protected content with current security configuration on this browser. Try playing in another browser.'
888- ) ;
889- // Should we flag this as a business exception?
890- const mediaError = new MediaError ( message , MediaError . MEDIA_ERR_ENCRYPTED , true ) ;
891- mediaError . errorCategory = MuxErrorCategory . DRM ;
892- mediaError . muxCode = MuxErrorCode . ENCRYPTED_UNSUPPORTED_KEY_SYSTEM ;
893- saveAndDispatchError ( mediaEl , mediaError ) ;
894- } ) ;
862+ const setupMediaKeys = async (
863+ props : Partial <
864+ Pick < MuxMediaPropsInternal , 'playbackId' | 'tokens' | 'playbackToken' | 'customDomain' | 'drmTypeCb' >
865+ > ,
866+ mediaEl : HTMLMediaElement ,
867+ initDataType : string
868+ ) => {
869+ const access = await navigator
870+ . requestMediaKeySystemAccess ( 'com.apple.fps' , [
871+ {
872+ initDataTypes : [ initDataType ] ,
873+ videoCapabilities : [ { contentType : 'application/vnd.apple.mpegurl' , robustness : '' } ] ,
874+ distinctiveIdentifier : 'not-allowed' ,
875+ persistentState : 'not-allowed' ,
876+ sessionTypes : [ 'temporary' ] ,
877+ } ,
878+ ] )
879+ . then ( ( value ) => {
880+ props . drmTypeCb ?.( DRMType . FAIRPLAY ) ;
881+ return value ;
882+ } )
883+ . catch ( ( ) => {
884+ const message = i18n (
885+ 'Cannot play DRM-protected content with current security configuration on this browser. Try playing in another browser.'
886+ ) ;
887+ // Should we flag this as a business exception?
888+ const mediaError = new MediaError ( message , MediaError . MEDIA_ERR_ENCRYPTED , true ) ;
889+ mediaError . errorCategory = MuxErrorCategory . DRM ;
890+ mediaError . muxCode = MuxErrorCode . ENCRYPTED_UNSUPPORTED_KEY_SYSTEM ;
891+ saveAndDispatchError ( mediaEl , mediaError ) ;
892+ } ) ;
895893
896- if ( ! access ) return ;
894+ if ( ! access ) return ;
897895
898- const keys = await access . createMediaKeys ( ) ;
896+ const keys = await access . createMediaKeys ( ) ;
899897
900- try {
901- const fairPlayAppCert = await getAppCertificate ( toAppCertURL ( props , 'fairplay' ) ) . catch ( ( errOrResp ) => {
902- if ( errOrResp instanceof Response ) {
903- const mediaError = getErrorFromResponse ( errOrResp , MuxErrorCategory . DRM , props ) ;
904- console . error ( 'mediaError' , mediaError ?. message , mediaError ?. context ) ;
905- if ( mediaError ) {
906- return Promise . reject ( mediaError ) ;
907- }
908- // NOTE: This should never happen. Adding for exhaustiveness (CJP).
909- return Promise . reject ( new Error ( 'Unexpected error in app cert request' ) ) ;
910- }
911- return Promise . reject ( errOrResp ) ;
912- } ) ;
913- await keys . setServerCertificate ( fairPlayAppCert ) . catch ( ( ) => {
914- const message = i18n (
915- 'Your server certificate failed when attempting to set it. This may be an issue with a no longer valid certificate.'
916- ) ;
917- const mediaError = new MediaError ( message , MediaError . MEDIA_ERR_ENCRYPTED , true ) ;
918- mediaError . errorCategory = MuxErrorCategory . DRM ;
919- mediaError . muxCode = MuxErrorCode . ENCRYPTED_UPDATE_SERVER_CERT_FAILED ;
898+ try {
899+ const fairPlayAppCert = await getAppCertificate ( toAppCertURL ( props , 'fairplay' ) ) . catch ( ( errOrResp ) => {
900+ if ( errOrResp instanceof Response ) {
901+ const mediaError = getErrorFromResponse ( errOrResp , MuxErrorCategory . DRM , props ) ;
902+ console . error ( 'mediaError' , mediaError ?. message , mediaError ?. context ) ;
903+ if ( mediaError ) {
920904 return Promise . reject ( mediaError ) ;
921- } ) ;
922- // @ts -ignore
923- } catch ( error : Error | MediaError ) {
924- saveAndDispatchError ( mediaEl , error ) ;
925- return ;
905+ }
906+ // NOTE: This should never happen. Adding for exhaustiveness (CJP).
907+ return Promise . reject ( new Error ( 'Unexpected error in app cert request' ) ) ;
926908 }
927- await mediaEl . setMediaKeys ( keys ) ;
928- }
929-
930- const initData = event . initData ;
931- if ( initData == null ) {
932- console . error ( `Could not start encrypted playback due to missing initData in ${ event . type } event` ) ;
933- return ;
934- }
909+ return Promise . reject ( errOrResp ) ;
910+ } ) ;
911+ await keys . setServerCertificate ( fairPlayAppCert ) . catch ( ( ) => {
912+ const message = i18n (
913+ 'Your server certificate failed when attempting to set it. This may be an issue with a no longer valid certificate.'
914+ ) ;
915+ const mediaError = new MediaError ( message , MediaError . MEDIA_ERR_ENCRYPTED , true ) ;
916+ mediaError . errorCategory = MuxErrorCategory . DRM ;
917+ mediaError . muxCode = MuxErrorCode . ENCRYPTED_UPDATE_SERVER_CERT_FAILED ;
918+ return Promise . reject ( mediaError ) ;
919+ } ) ;
920+ // @ts -ignore
921+ } catch ( error : Error | MediaError ) {
922+ saveAndDispatchError ( mediaEl , error ) ;
923+ return ;
924+ }
925+ await mediaEl . setMediaKeys ( keys ) ;
926+ } ;
935927
936- const session = ( mediaEl . mediaKeys as MediaKeys ) . createSession ( ) ;
937- session . addEventListener ( 'keystatuseschange' , ( ) => {
938- // recheck key statuses
939- // NOTE: As an improvement, we could also add checks for a status of 'expired' and
940- // attempt to renew the license here (CJP)
941- session . keyStatuses . forEach ( ( mediaKeyStatus ) => {
942- let mediaError ;
943- if ( mediaKeyStatus === 'internal-error' ) {
944- const message = i18n (
945- 'The DRM Content Decryption Module system had an internal failure. Try reloading the page, upading your browser, or playing in another browser.'
946- ) ;
947- mediaError = new MediaError ( message , MediaError . MEDIA_ERR_ENCRYPTED , true ) ;
948- mediaError . errorCategory = MuxErrorCategory . DRM ;
949- mediaError . muxCode = MuxErrorCode . ENCRYPTED_CDM_ERROR ;
950- } else if ( mediaKeyStatus === 'output-restricted' || mediaKeyStatus === 'output-downscaled' ) {
951- const message = i18n (
952- 'DRM playback is being attempted in an environment that is not sufficiently secure. User may see black screen.'
953- ) ;
954- // NOTE: When encountered, this is a non-fatal error (though it's certainly interruptive of standard playback experience). (CJP)
955- mediaError = new MediaError ( message , MediaError . MEDIA_ERR_ENCRYPTED , false ) ;
956- mediaError . errorCategory = MuxErrorCategory . DRM ;
957- mediaError . muxCode = MuxErrorCode . ENCRYPTED_OUTPUT_RESTRICTED ;
958- }
928+ const updateMediaKeyStatus = ( mediaEl : HTMLMediaElement , mediaKeyStatus : MediaKeyStatus ) => {
929+ let mediaError ;
930+ if ( mediaKeyStatus === 'internal-error' ) {
931+ const message = i18n (
932+ 'The DRM Content Decryption Module system had an internal failure. Try reloading the page, upading your browser, or playing in another browser.'
933+ ) ;
934+ mediaError = new MediaError ( message , MediaError . MEDIA_ERR_ENCRYPTED , true ) ;
935+ mediaError . errorCategory = MuxErrorCategory . DRM ;
936+ mediaError . muxCode = MuxErrorCode . ENCRYPTED_CDM_ERROR ;
937+ } else if ( mediaKeyStatus === 'output-restricted' || mediaKeyStatus === 'output-downscaled' ) {
938+ const message = i18n (
939+ 'DRM playback is being attempted in an environment that is not sufficiently secure. User may see black screen.'
940+ ) ;
941+ // NOTE: When encountered, this is a non-fatal error (though it's certainly interruptive of standard playback experience). (CJP)
942+ mediaError = new MediaError ( message , MediaError . MEDIA_ERR_ENCRYPTED , false ) ;
943+ mediaError . errorCategory = MuxErrorCategory . DRM ;
944+ mediaError . muxCode = MuxErrorCode . ENCRYPTED_OUTPUT_RESTRICTED ;
945+ }
959946
960- if ( mediaError ) {
961- saveAndDispatchError ( mediaEl , mediaError ) ;
962- }
963- } ) ;
964- } ) ;
965- const message = await Promise . all ( [
966- session . generateRequest ( initDataType , initData ) . catch ( ( ) => {
967- // eslint-disable-next-line no-shadow
968- const message = i18n (
969- 'Failed to generate a DRM license request. This may be an issue with the player or your protected content.'
970- ) ;
971- const mediaError = new MediaError ( message , MediaError . MEDIA_ERR_ENCRYPTED , true ) ;
972- mediaError . errorCategory = MuxErrorCategory . DRM ;
973- mediaError . muxCode = MuxErrorCode . ENCRYPTED_GENERATE_REQUEST_FAILED ;
974- saveAndDispatchError ( mediaEl , mediaError ) ;
975- } ) ,
976- new Promise < MediaKeyMessageEvent [ 'message' ] > ( ( resolve ) => {
977- session . addEventListener (
978- 'message' ,
979- ( messageEvent ) => {
980- resolve ( messageEvent . message ) ;
981- } ,
982- { once : true }
983- ) ;
984- } ) ,
985- ] ) . then ( ( [ , messageEventMsg ] ) => messageEventMsg ) ;
947+ if ( mediaError ) {
948+ saveAndDispatchError ( mediaEl , mediaError ) ;
949+ }
950+ } ;
986951
987- const response = await getLicenseKey ( message , toLicenseKeyURL ( props , 'fairplay' ) ) . catch ( ( errOrResp ) => {
952+ const setupMediaKeySession = async (
953+ props : Partial <
954+ Pick < MuxMediaPropsInternal , 'playbackId' | 'tokens' | 'playbackToken' | 'customDomain' | 'drmTypeCb' >
955+ > ,
956+ mediaEl : HTMLMediaElement ,
957+ initDataType : string ,
958+ initData : ArrayBuffer
959+ ) => {
960+ const session = ( mediaEl . mediaKeys as MediaKeys ) . createSession ( ) ;
961+ session . addEventListener ( 'keystatuseschange' , ( ) => {
962+ // recheck key statuses
963+ // NOTE: As an improvement, we could also add checks for a status of 'expired' and
964+ // attempt to renew the license here (CJP)
965+ session . keyStatuses . forEach ( ( keyStatus ) => updateMediaKeyStatus ( mediaEl , keyStatus ) ) ;
966+ } ) ;
967+ session . generateRequest ( initDataType , initData ) . catch ( ( e ) => {
968+ console . error ( 'Failed to generate license request' , e ) ;
969+ const message = i18n (
970+ 'Failed to generate a DRM license request. This may be an issue with the player or your protected content.'
971+ ) ;
972+ const mediaError = new MediaError ( message , MediaError . MEDIA_ERR_ENCRYPTED , true ) ;
973+ mediaError . errorCategory = MuxErrorCategory . DRM ;
974+ mediaError . muxCode = MuxErrorCode . ENCRYPTED_GENERATE_REQUEST_FAILED ;
975+ saveAndDispatchError ( mediaEl , mediaError ) ;
976+ } ) ;
977+ session . addEventListener ( 'message' , async ( event ) => {
978+ const spc = event . message ;
979+ const ckc = await getLicenseKey ( spc , toLicenseKeyURL ( props , 'fairplay' ) ) . catch ( ( errOrResp ) => {
988980 if ( errOrResp instanceof Response ) {
989981 const mediaError = getErrorFromResponse ( errOrResp , MuxErrorCategory . DRM , props ) ;
990982 console . error ( 'mediaError' , mediaError ?. message , mediaError ?. context ) ;
@@ -996,16 +988,41 @@ export const setupNativeFairplayDRM = (
996988 }
997989 return Promise . reject ( errOrResp ) ;
998990 } ) ;
999- await session . update ( response ) . catch ( ( ) => {
1000- // eslint-disable-next-line no-shadow
991+
992+ // This is the same call whether we are local or AirPlay.
993+ // Safari will forward CKC to Apple TV automatically.
994+ await session . update ( ckc ) . catch ( ( ) => {
1001995 const message = i18n (
1002996 'Failed to update DRM license. This may be an issue with the player or your protected content.'
1003997 ) ;
1004998 const mediaError = new MediaError ( message , MediaError . MEDIA_ERR_ENCRYPTED , true ) ;
1005999 mediaError . errorCategory = MuxErrorCategory . DRM ;
10061000 mediaError . muxCode = MuxErrorCode . ENCRYPTED_UPDATE_LICENSE_FAILED ;
1007- return Promise . reject ( mediaError ) ;
1001+
1002+ saveAndDispatchError ( mediaEl , mediaError ) ;
10081003 } ) ;
1004+ } ) ;
1005+ } ;
1006+
1007+ const onFpEncrypted = async ( event : MediaEncryptedEvent ) => {
1008+ try {
1009+ const initDataType = event . initDataType ;
1010+ if ( initDataType !== 'skd' ) {
1011+ console . error ( `Received unexpected initialization data type "${ initDataType } "` ) ;
1012+ return ;
1013+ }
1014+
1015+ if ( ! mediaEl . mediaKeys ) {
1016+ await setupMediaKeys ( props , mediaEl , initDataType ) ;
1017+ }
1018+
1019+ const initData = event . initData ;
1020+ if ( initData == null ) {
1021+ console . error ( `Could not start encrypted playback due to missing initData in ${ event . type } event` ) ;
1022+ return ;
1023+ }
1024+
1025+ await setupMediaKeySession ( props , mediaEl , initDataType , initData ) ;
10091026 // @ts -ignore
10101027 } catch ( error : Error | MediaError ) {
10111028 saveAndDispatchError ( mediaEl , error ) ;
0 commit comments