import { detect } from 'detect-browser' ; import BrowserBehavior from './BrowserBehavior' ; import ExtendedBrowserBehavior from './ExtendedBrowserBehavior' ; export default class DefaultBrowserBehavior implements BrowserBehavior , ExtendedBrowserBehavior { private readonly browser = detect ( ) ; private browserSupport : { [ id ] : number ; } = { chrome : 78 , 'edge-chromium' : 79 , electron : 7 , firefox : 75 , ios : 12 , safari : 12 , opera : 66 , samsung : 12 , crios : 86 , fxios : 23 , 'ios-webview' : 605 , 'chromium-webview' : 92 , } ; private browserName : { [ id ] : string ; } = { chrome : 'Google Chrome' , 'edge-chromium' : 'Microsoft Edge' , electron : 'Electron' , firefox : 'Mozilla Firefox' , ios : 'Safari iOS' , safari : 'Safari' , opera : 'Opera' , samsung : 'Samsung Internet' , crios : 'Chrome iOS' , fxios : 'Firefox iOS' , 'ios-webview' : 'WKWebView iOS' , 'chromium-webview' : 'Chrome WebView' , } ; private chromeLike : string [ ] = [ 'chrome' , 'edge-chromium' , 'chromium-webview' , 'opera' , 'samsung' , ] ; private webkitBrowsers : string [ ] = [ 'crios' , 'fxios' , 'safari' , 'ios' , 'ios-webview' ] ; private recreateAudioContextIfNeeded : boolean ; constructor ( { recreateAudioContextIfNeeded = false , } = { } ) { this . recreateAudioContextIfNeeded = recreateAudioContextIfNeeded ; } version ( ) { return this . browser . version ; } majorVersion ( ) { return parseInt ( this . version ( ) . split ( '.' ) [ 0 ] ) ; } name ( ) { return this . browser . name ; } hasChromiumWebRTC ( ) { for ( const browser of this . chromeLike ) { if ( browser === this . browser . name ) { return true ; } } return false ; } hasWebKitWebRTC ( ) { for ( const browser of this . webkitBrowsers ) { if ( browser === this . browser . name ) { return true ; } } return false ; } hasFirefoxWebRTC ( ) { return this . isFirefox ( ) ; } supportsCanvasCapturedStreamPlayback ( ) { return ! this . isIOSSafari ( ) && ! this . isIOSChrome ( ) && ! this . isIOSFirefox ( ) ; } supportsBackgroundFilter ( ) { if ( this . name ( ) === 'safari' && this . majorVersion ( ) === 15 ) { return false ; } if ( ! this . supportsCanvasCapturedStreamPlayback ( ) ) { return false ; } return true ; } requiresUnifiedPlan ( ) { return ( this . isFirefox ( ) || this . hasChromiumWebRTC ( ) || ( this . hasWebKitWebRTC ( ) && this . isUnifiedPlanSupported ( ) ) ) ; } requiresResolutionAlignment ( width , height ) { if ( this . isAndroid ( ) && this . isPixel3 ( ) ) { return [ Math . ceil ( width / 64 ) * 64 , Math . ceil ( height / 64 ) * 64 ] ; } return [ width , height ] ; } requiresCheckForSdpConnectionAttributes ( ) { return ! this . isIOSSafari ( ) && ! this . isIOSChrome ( ) && ! this . isIOSFirefox ( ) ; } requiresIceCandidateGatheringTimeoutWorkaround ( ) { return this . hasChromiumWebRTC ( ) ; } requiresUnifiedPlanMunging ( ) { return this . hasChromiumWebRTC ( ) || ( this . hasWebKitWebRTC ( ) && this . isUnifiedPlanSupported ( ) ) ; } requiresSortCodecPreferencesForSdpAnswer ( ) { return this . isFirefox ( ) && this . majorVersion ( ) <= 68 ; } requiresSimulcastMunging ( ) { return this . isSafari ( ) ; } requiresBundlePolicy ( ) { return 'max-bundle' ; } requiresPromiseBasedWebRTCGetStats ( ) { return ! this . hasChromiumWebRTC ( ) ; } requiresVideoElementWorkaround ( ) { return this . isSafari ( ) && this . majorVersion ( ) === 12 ; } requiresNoExactMediaStreamConstraints ( ) { return ( this . isSamsungInternet ( ) || ( this . isIOSSafari ( ) && ( this . version ( ) === '12.0.0' || this . version ( ) === '12.1.0' ) ) ) ; } requiresGroupIdMediaStreamConstraints ( ) { return this . isSamsungInternet ( ) ; } requiresContextRecreationForAudioWorklet ( ) { if ( ! this . recreateAudioContextIfNeeded ) { return false ; } if ( ! ( 'chrome' in global ) ) { return false ; } if ( this . browser . os !== 'Mac OS' ) { return false ; } if ( this . isChrome ( ) || this . isEdge ( ) ) { return true ; } return false ; } getDisplayMediaAudioCaptureSupport ( ) { return this . isChrome ( ) || this . isEdge ( ) ; } supportsSenderSideBandwidthEstimation ( ) { return this . hasChromiumWebRTC ( ) || this . isSafari ( ) ; } doesNotSupportMediaDeviceLabels ( ) { return this . browser . name === 'chromium-webview' ; } screenShareUnsupported ( ) { console . warn ( 'This function is no longer supported.' ) ; return true ; } isSupported ( ) { if ( ! this . browserSupport [ this . browser . name ] || this . majorVersion ( ) < this . browserSupport [ this . browser . name ] ) { return false ; } if ( this . browser . name === 'firefox' && this . isAndroid ( ) ) { return false ; } return true ; } isSimulcastSupported ( ) { return this . hasChromiumWebRTC ( ) ; } supportDownlinkBandwidthEstimation ( ) { return ! this . isFirefox ( ) ; } supportString ( ) { if ( this . isAndroid ( ) ) { return `${ this . browserName [ 'chrome' ] } ${ this . browserSupport [ 'chrome' ] }+, ${ this . browserName [ 'samsung' ] } ${ this . browserSupport [ 'samsung' ] }+` ; } const s = [ ] ; for ( const k in this . browserSupport ) { s . push ( `${ this . browserName [ k ] } ${ this . browserSupport [ k ] }+` ) ; } return s . join ( ', ' ) ; } async supportedVideoCodecs ( ) { const pc = new RTCPeerConnection ( ) ; pc . addTransceiver ( 'video' , { direction : 'inactive' , streams : [ ] } ) ; return ( await pc . createOffer ( { offerToReceiveVideo : true } ) ) . sdp . split ( '\r\n' ) . filter ( x => { return x . includes ( 'a=rtpmap:' ) ; } ) . map ( x => { return x . replace ( /.* / , '' ) . replace ( /\/.*/ , '' ) ; } ) . filter ( ( v , i , a ) => { return a . indexOf ( v ) === i ; } ) . filter ( x => { return x !== 'rtx' && x !== 'red' && x !== 'ulpfec' ; } ) ; } supportsSetSinkId ( ) { return 'setSinkId' in HTMLAudioElement . prototype ; } disableResolutionScaleDown ( ) { return this . isAndroid ( ) ; } requiresDisablingH264Encoding ( ) { return ( ( this . isIOSSafari ( ) || this . isIOSChrome ( ) || this . isIOSFirefox ( ) ) && ( this . version ( ) === '15.1.0' || /( OS 15_1)/i . test ( navigator . userAgent ) ) ) ; } private isIOSSafari ( ) { return ( this . browser . name === 'ios' || this . browser . name === 'ios-webview' || ( this . browser . name === 'safari' && /( Mac )/i . test ( navigator . userAgent ) && navigator . maxTouchPoints > 1 ) ) ; } private isSafari ( ) { return this . browser . name === 'safari' || this . browser . name === 'ios' ; } private isFirefox ( ) { return this . browser . name === 'firefox' ; } private isIOSFirefox ( ) { return this . browser . name === 'fxios' ; } private isIOSChrome ( ) { return this . browser . name === 'crios' ; } private isChrome ( ) { return this . browser . name === 'chrome' ; } private isEdge ( ) { return this . browser . name === 'edge-chromium' ; } private isSamsungInternet ( ) { return this . browser . name === 'samsung' ; } private isAndroid ( ) { return /(android)/i . test ( navigator . userAgent ) ; } private isPixel3 ( ) { return /( pixel 3)/i . test ( navigator . userAgent ) ; } private isUnifiedPlanSupported ( ) { return RTCRtpTransceiver . prototype . hasOwnProperty ( 'currentDirection' ) ; } }