Ott 1997 ios live btn

keep-around/401f9622901491e44aff8564418e9f565b5f439a
Дектерев Андрей 4 years ago committed by Макситалиев Мирлан
parent 023687a6c0
commit 401f962290
  1. 114
      package-lock.json
  2. 5
      package.json
  3. 6
      src/config/userAgent.tsx
  4. 4
      src/features/StreamPlayer/components/ProgressBar/index.tsx
  5. 6
      src/features/StreamPlayer/config.tsx
  6. 17
      src/features/StreamPlayer/hooks/index.tsx
  7. 4
      src/features/StreamPlayer/hooks/useFullscreen.tsx
  8. 7
      src/features/StreamPlayer/hooks/useHlsPlayer.tsx
  9. 6
      src/features/StreamPlayer/hooks/useVideoQuality.tsx
  10. 18
      src/features/StreamPlayer/index.tsx
  11. 16
      src/helpers/parseHlsResponse/index.ts
  12. 22
      src/index.tsx
  13. 51
      src/service-worker.ts
  14. 54
      src/serviceWorker.ts
  15. 70
      src/types/index.d.ts
  16. 5
      tsconfig.json

114
package-lock.json generated

@ -6692,6 +6692,16 @@
"eslint-visitor-keys": "^2.0.0"
}
},
"@videojs/vhs-utils": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@videojs/vhs-utils/-/vhs-utils-3.0.4.tgz",
"integrity": "sha512-hui4zOj2I1kLzDgf8QDVxD3IzrwjS/43KiS8IHQO0OeeSsb4pB/lgNt1NG7Dv0wMQfCccUpMVLGcK618s890Yg==",
"requires": {
"@babel/runtime": "^7.12.5",
"global": "^4.4.0",
"url-toolkit": "^2.2.1"
}
},
"@webassemblyjs/ast": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz",
@ -10553,8 +10563,7 @@
"dom-walk": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz",
"integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==",
"dev": true
"integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w=="
},
"domain-browser": {
"version": "1.2.0",
@ -12943,7 +12952,6 @@
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz",
"integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==",
"dev": true,
"requires": {
"min-document": "^2.19.0",
"process": "^0.11.10"
@ -16744,6 +16752,16 @@
"integrity": "sha1-wNjq82BZ9wV5bh40SBHPTEmNOiY=",
"dev": true
},
"m3u8-parser": {
"version": "4.7.0",
"resolved": "https://registry.npmjs.org/m3u8-parser/-/m3u8-parser-4.7.0.tgz",
"integrity": "sha512-48l/OwRyjBm+QhNNigEEcRcgbRvnUjL7rxs597HmW9QSNbyNvt+RcZ9T/d9vxi9A9z7EZrB1POtZYhdRlwYQkQ==",
"requires": {
"@babel/runtime": "^7.12.5",
"@videojs/vhs-utils": "^3.0.0",
"global": "^4.4.0"
}
},
"magic-string": {
"version": "0.25.7",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz",
@ -17122,7 +17140,6 @@
"version": "2.19.0",
"resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz",
"integrity": "sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU=",
"dev": true,
"requires": {
"dom-walk": "^0.1.0"
}
@ -25118,6 +25135,13 @@
"integrity": "sha512-AH6x5pYq4vwQvfRDWH+vfOePfPIYQ00nCEB7dJRU1e0n9+9HMRyvI63FlDvtFT2AvXVRsXvUt7DNMEToyJLpSA==",
"requires": {
"workbox-core": "^5.1.4"
},
"dependencies": {
"workbox-core": {
"version": "5.1.4",
"resolved": "https://registry.npmjs.org/workbox-core/-/workbox-core-5.1.4.tgz",
"integrity": "sha512-+4iRQan/1D8I81nR2L5vcbaaFskZC2CL17TLbvWVzQ4qiF/ytOGF6XeV54pVxAvKUtkLANhk8TyIUMtiMw2oDg=="
}
}
},
"workbox-broadcast-update": {
@ -25126,6 +25150,13 @@
"integrity": "sha512-HTyTWkqXvHRuqY73XrwvXPud/FN6x3ROzkfFPsRjtw/kGZuZkPzfeH531qdUGfhtwjmtO/ZzXcWErqVzJNdXaA==",
"requires": {
"workbox-core": "^5.1.4"
},
"dependencies": {
"workbox-core": {
"version": "5.1.4",
"resolved": "https://registry.npmjs.org/workbox-core/-/workbox-core-5.1.4.tgz",
"integrity": "sha512-+4iRQan/1D8I81nR2L5vcbaaFskZC2CL17TLbvWVzQ4qiF/ytOGF6XeV54pVxAvKUtkLANhk8TyIUMtiMw2oDg=="
}
}
},
"workbox-build": {
@ -25198,6 +25229,11 @@
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="
},
"workbox-core": {
"version": "5.1.4",
"resolved": "https://registry.npmjs.org/workbox-core/-/workbox-core-5.1.4.tgz",
"integrity": "sha512-+4iRQan/1D8I81nR2L5vcbaaFskZC2CL17TLbvWVzQ4qiF/ytOGF6XeV54pVxAvKUtkLANhk8TyIUMtiMw2oDg=="
}
}
},
@ -25207,6 +25243,13 @@
"integrity": "sha512-0bfvMZs0Of1S5cdswfQK0BXt6ulU5kVD4lwer2CeI+03czHprXR3V4Y8lPTooamn7eHP8Iywi5QjyAMjw0qauA==",
"requires": {
"workbox-core": "^5.1.4"
},
"dependencies": {
"workbox-core": {
"version": "5.1.4",
"resolved": "https://registry.npmjs.org/workbox-core/-/workbox-core-5.1.4.tgz",
"integrity": "sha512-+4iRQan/1D8I81nR2L5vcbaaFskZC2CL17TLbvWVzQ4qiF/ytOGF6XeV54pVxAvKUtkLANhk8TyIUMtiMw2oDg=="
}
}
},
"workbox-core": {
@ -25220,6 +25263,13 @@
"integrity": "sha512-oDO/5iC65h2Eq7jctAv858W2+CeRW5e0jZBMNRXpzp0ZPvuT6GblUiHnAsC5W5lANs1QS9atVOm4ifrBiYY7AQ==",
"requires": {
"workbox-core": "^5.1.4"
},
"dependencies": {
"workbox-core": {
"version": "5.1.4",
"resolved": "https://registry.npmjs.org/workbox-core/-/workbox-core-5.1.4.tgz",
"integrity": "sha512-+4iRQan/1D8I81nR2L5vcbaaFskZC2CL17TLbvWVzQ4qiF/ytOGF6XeV54pVxAvKUtkLANhk8TyIUMtiMw2oDg=="
}
}
},
"workbox-google-analytics": {
@ -25231,6 +25281,13 @@
"workbox-core": "^5.1.4",
"workbox-routing": "^5.1.4",
"workbox-strategies": "^5.1.4"
},
"dependencies": {
"workbox-core": {
"version": "5.1.4",
"resolved": "https://registry.npmjs.org/workbox-core/-/workbox-core-5.1.4.tgz",
"integrity": "sha512-+4iRQan/1D8I81nR2L5vcbaaFskZC2CL17TLbvWVzQ4qiF/ytOGF6XeV54pVxAvKUtkLANhk8TyIUMtiMw2oDg=="
}
}
},
"workbox-navigation-preload": {
@ -25239,6 +25296,13 @@
"integrity": "sha512-Wf03osvK0wTflAfKXba//QmWC5BIaIZARU03JIhAEO2wSB2BDROWI8Q/zmianf54kdV7e1eLaIEZhth4K4MyfQ==",
"requires": {
"workbox-core": "^5.1.4"
},
"dependencies": {
"workbox-core": {
"version": "5.1.4",
"resolved": "https://registry.npmjs.org/workbox-core/-/workbox-core-5.1.4.tgz",
"integrity": "sha512-+4iRQan/1D8I81nR2L5vcbaaFskZC2CL17TLbvWVzQ4qiF/ytOGF6XeV54pVxAvKUtkLANhk8TyIUMtiMw2oDg=="
}
}
},
"workbox-precaching": {
@ -25247,6 +25311,13 @@
"integrity": "sha512-gCIFrBXmVQLFwvAzuGLCmkUYGVhBb7D1k/IL7pUJUO5xacjLcFUaLnnsoVepBGAiKw34HU1y/YuqvTKim9qAZA==",
"requires": {
"workbox-core": "^5.1.4"
},
"dependencies": {
"workbox-core": {
"version": "5.1.4",
"resolved": "https://registry.npmjs.org/workbox-core/-/workbox-core-5.1.4.tgz",
"integrity": "sha512-+4iRQan/1D8I81nR2L5vcbaaFskZC2CL17TLbvWVzQ4qiF/ytOGF6XeV54pVxAvKUtkLANhk8TyIUMtiMw2oDg=="
}
}
},
"workbox-range-requests": {
@ -25255,6 +25326,13 @@
"integrity": "sha512-1HSujLjgTeoxHrMR2muDW2dKdxqCGMc1KbeyGcmjZZAizJTFwu7CWLDmLv6O1ceWYrhfuLFJO+umYMddk2XMhw==",
"requires": {
"workbox-core": "^5.1.4"
},
"dependencies": {
"workbox-core": {
"version": "5.1.4",
"resolved": "https://registry.npmjs.org/workbox-core/-/workbox-core-5.1.4.tgz",
"integrity": "sha512-+4iRQan/1D8I81nR2L5vcbaaFskZC2CL17TLbvWVzQ4qiF/ytOGF6XeV54pVxAvKUtkLANhk8TyIUMtiMw2oDg=="
}
}
},
"workbox-routing": {
@ -25263,6 +25341,13 @@
"integrity": "sha512-8ljknRfqE1vEQtnMtzfksL+UXO822jJlHTIR7+BtJuxQ17+WPZfsHqvk1ynR/v0EHik4x2+826Hkwpgh4GKDCw==",
"requires": {
"workbox-core": "^5.1.4"
},
"dependencies": {
"workbox-core": {
"version": "5.1.4",
"resolved": "https://registry.npmjs.org/workbox-core/-/workbox-core-5.1.4.tgz",
"integrity": "sha512-+4iRQan/1D8I81nR2L5vcbaaFskZC2CL17TLbvWVzQ4qiF/ytOGF6XeV54pVxAvKUtkLANhk8TyIUMtiMw2oDg=="
}
}
},
"workbox-strategies": {
@ -25272,6 +25357,13 @@
"requires": {
"workbox-core": "^5.1.4",
"workbox-routing": "^5.1.4"
},
"dependencies": {
"workbox-core": {
"version": "5.1.4",
"resolved": "https://registry.npmjs.org/workbox-core/-/workbox-core-5.1.4.tgz",
"integrity": "sha512-+4iRQan/1D8I81nR2L5vcbaaFskZC2CL17TLbvWVzQ4qiF/ytOGF6XeV54pVxAvKUtkLANhk8TyIUMtiMw2oDg=="
}
}
},
"workbox-streams": {
@ -25281,6 +25373,13 @@
"requires": {
"workbox-core": "^5.1.4",
"workbox-routing": "^5.1.4"
},
"dependencies": {
"workbox-core": {
"version": "5.1.4",
"resolved": "https://registry.npmjs.org/workbox-core/-/workbox-core-5.1.4.tgz",
"integrity": "sha512-+4iRQan/1D8I81nR2L5vcbaaFskZC2CL17TLbvWVzQ4qiF/ytOGF6XeV54pVxAvKUtkLANhk8TyIUMtiMw2oDg=="
}
}
},
"workbox-sw": {
@ -25307,6 +25406,13 @@
"integrity": "sha512-vXQtgTeMCUq/4pBWMfQX8Ee7N2wVC4Q7XYFqLnfbXJ2hqew/cU1uMTD2KqGEgEpE4/30luxIxgE+LkIa8glBYw==",
"requires": {
"workbox-core": "^5.1.4"
},
"dependencies": {
"workbox-core": {
"version": "5.1.4",
"resolved": "https://registry.npmjs.org/workbox-core/-/workbox-core-5.1.4.tgz",
"integrity": "sha512-+4iRQan/1D8I81nR2L5vcbaaFskZC2CL17TLbvWVzQ4qiF/ytOGF6XeV54pVxAvKUtkLANhk8TyIUMtiMw2oDg=="
}
}
},
"worker-farm": {

@ -23,6 +23,7 @@
"history": "^4.10.1",
"hls.js": "^0.14.15",
"lodash": "^4.17.15",
"m3u8-parser": "^4.7.0",
"oidc-client": "^1.11.5",
"react": "^17.0.2",
"react-datepicker": "^3.1.3",
@ -32,7 +33,9 @@
"react-scripts": "^4.0.3",
"react-window": "^1.8.6",
"screenfull": "^5.0.2",
"styled-components": "^5.3.3"
"styled-components": "^5.3.3",
"workbox-core": "^5.1.4",
"workbox-precaching": "^5.1.4"
},
"devDependencies": {
"@commitlint/cli": "^14.1.0",

@ -1,5 +1,3 @@
import includes from 'lodash/includes'
export const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent)
export const isIphone = includes(window.navigator.userAgent, 'iPhone')
export const isMobileDevice = includes(window.navigator.userAgent, 'Android') || isIphone
export const isMobileDevice = /iPhone|Android/.test(navigator.userAgent)

@ -24,8 +24,8 @@ export const ProgressBar = ({
playedProgress,
}: Props) => {
const progressBarRef = useSlider({ onChange: onPlayedProgressChange })
const loadedFraction = loadedProgress * 100 / duration
const playedFraction = playedProgress * 100 / duration
const loadedFraction = Math.min(loadedProgress * 100 / duration, 100)
const playedFraction = Math.min(playedProgress * 100 / duration, 100)
return (
<ProgressBarList ref={progressBarRef}>

@ -1,19 +1,13 @@
import Hls from 'hls.js'
import { readToken } from 'helpers/token'
import { isIphone } from 'config/userAgent'
export const streamConfig: Partial<Hls.Config> = {
liveSyncDuration: 30,
maxBufferLength: 30,
xhrSetup: (xhr, urlString) => {
if (isIphone) {
// eslint-disable-next-line no-param-reassign
xhr.withCredentials = true
} else {
const url = new URL(urlString)
url.searchParams.set('access_token', readToken() || '')
xhr.open('GET', url.toString())
}
},
}

@ -15,6 +15,8 @@ import { useObjectState } from 'hooks'
import type { MatchInfo } from 'requests/getMatchInfo'
import { isIOS } from 'config/userAgent'
import { useHlsPlayer } from './useHlsPlayer'
import { useFullscreen } from './useFullscreen'
import { useVideoQuality } from './useVideoQuality'
@ -107,13 +109,14 @@ export const useVideoPlayer = ({
const onLoadedProgress = (loadedMs: number) => {
setPlayerState({ loadedProgress: loadedMs })
}
const onPlayedProgress = (playedMs: number) => {
setPlayerState({ playedProgress: playedMs })
progressChangeCallback(playedMs / 1000)
}
const backToLive = useCallback(() => {
const liveProgressMs = duration - 10000
const liveProgressMs = Math.max(duration - 10000, 0)
setPlayerState({ playedProgress: liveProgressMs, seek: liveProgressMs / 1000 })
}, [duration, setPlayerState])
@ -130,6 +133,18 @@ export const useVideoPlayer = ({
setPlayerState,
])
useEffect(() => {
if (!navigator.serviceWorker || !isIOS) return undefined
const listener = (event: MessageEvent) => {
setPlayerState({ duration: toMilliSeconds(event.data.duration) })
}
navigator.serviceWorker.addEventListener('message', listener)
return () => {
navigator.serviceWorker.removeEventListener('message', listener)
}
}, [setPlayerState])
return {
backToLive,
duration,

@ -1,7 +1,7 @@
import { useEffect, useRef } from 'react'
import screenfull from 'screenfull'
import { isIphone } from 'config/userAgent'
import { isIOS } from 'config/userAgent'
import { useToggle } from 'hooks'
@ -62,7 +62,7 @@ export const useFullscreen = () => {
}
const onFullscreenClick = () => {
if (isIphone) {
if (isIOS) {
toggleIOSFullscreen()
} else {
toggleFullscreen()

@ -11,23 +11,26 @@ import { streamConfig } from '../config'
export const useHlsPlayer = (src: string, resumeFrom?: number) => {
const hls = useMemo(() => {
if (!Hls.isSupported()) return null
const newStreamConfig = { ...streamConfig }
if (isNumber(resumeFrom)) {
newStreamConfig.startPosition = resumeFrom
}
return new Hls(newStreamConfig)
}, [resumeFrom])
const videoRef = useRef<HTMLVideoElement>(null)
useEffect(() => {
const video = videoRef.current
if (!video) return
if (!video || !hls) return
hls.loadSource(src)
hls.attachMedia(video)
}, [src, hls])
useEffect(() => () => hls.destroy(), [hls])
useEffect(() => () => hls?.destroy(), [hls])
return {
hls,

@ -46,7 +46,7 @@ const getVideoQualities = (levels: Array<Hls.Level>) => {
return uniqBy([...sorted, autoQuality], 'label')
}
export const useVideoQuality = (hls: Hls) => {
export const useVideoQuality = (hls: Hls | null) => {
const [videoQualities, setVideoQualities] = useState([autoQuality])
const [selectedQuality, setSelectedQuality] = useLocalStore({
defaultValue: autoQuality.label,
@ -55,6 +55,8 @@ export const useVideoQuality = (hls: Hls) => {
})
const onQualitySelect = useCallback((label: string) => {
if (!hls) return
const quality = find(videoQualities, { label })
if (!quality || quality.level === hls.currentLevel) return
// eslint-disable-next-line no-param-reassign
@ -67,6 +69,8 @@ export const useVideoQuality = (hls: Hls) => {
])
useEffect(() => {
if (!hls) return undefined
const listener = () => {
const qualities = getVideoQualities(hls.levels)
const quality = find(qualities, { label: selectedQuality }) || autoQuality

@ -79,13 +79,11 @@ export const StreamPlayer = (props: Props) => {
onTouchStart={onTouchStart}
onTouchEnd={onTouchEnd}
>
{
!ready && (
{!ready && (
<LoaderWrapper>
<Loader color='#515151' />
</LoaderWrapper>
)
}
)}
<VideoPlayer
width='100%'
height='100%'
@ -104,9 +102,7 @@ export const StreamPlayer = (props: Props) => {
onError={onError}
crossOrigin='use-credentials'
/>
{
ready && (
{ready && (
<CenterControls playing={playing}>
<Backward size='lg' onClick={rewindBackward}>
{REWIND_SECONDS}
@ -121,8 +117,7 @@ export const StreamPlayer = (props: Props) => {
{REWIND_SECONDS}
</Forward>
</CenterControls>
)
}
)}
<Controls visible={controlsVisible}>
<ControlsRow>
@ -142,9 +137,7 @@ export const StreamPlayer = (props: Props) => {
onChange={onVolumeChange}
onClick={onVolumeClick}
/>
<PlaybackTime>
{secondsToHms(playedProgress / 1000)}
</PlaybackTime>
<PlaybackTime>{secondsToHms(playedProgress / 1000)}</PlaybackTime>
<Backward onClick={rewindBackward}>{REWIND_SECONDS}</Backward>
<Forward onClick={rewindForward}>{REWIND_SECONDS}</Forward>
</ControlsGroup>
@ -156,7 +149,6 @@ export const StreamPlayer = (props: Props) => {
</LiveBtn>
)
}
<Fullscreen
onClick={onFullscreenClick}
isFullscreen={isFullscreen}

@ -0,0 +1,16 @@
import { Parser, Manifest } from 'm3u8-parser'
import reduce from 'lodash/reduce'
export const manifestParser = (manifest: string) => {
const parser = new Parser()
parser.push(manifest)
parser.end()
const parsedManifest: Manifest = parser.manifest
const totalDuration = reduce(
parsedManifest?.segments,
(sum: number, { duration }) => sum + duration,
0,
)
return totalDuration
}

@ -5,6 +5,8 @@ import {
} from 'react'
import ReactDOM from 'react-dom'
import { isIOS } from 'config/userAgent'
import * as serviceWorker from './serviceWorker'
export const App = process.env.REACT_APP_TYPE === 'auth-service'
@ -20,7 +22,21 @@ ReactDOM.render(
document.getElementById('root'),
)
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
if (isIOS) {
serviceWorker.register({
onUpdate: (registration) => {
const waitingServiceWorker = registration.waiting
if (waitingServiceWorker) {
waitingServiceWorker.addEventListener('statechange', (event) => {
// @ts-expect-error
if (event.target?.state === 'activated') {
window.location.reload()
}
})
waitingServiceWorker.postMessage({ type: 'SKIP_WAITING' })
}
},
})
} else {
serviceWorker.unregister()
}

@ -0,0 +1,51 @@
/// <reference lib="webworker" />
/* eslint-disable no-restricted-globals */
// This service worker can be customized!
// See https://developers.google.com/web/tools/workbox/modules
// for the list of available Workbox modules, or add any other
// code you'd like.
// You can also remove this file if you'd prefer not to use a
// service worker, and the Workbox build step will be skipped.
import { clientsClaim } from 'workbox-core'
import { precacheAndRoute } from 'workbox-precaching'
import { manifestParser } from './helpers/parseHlsResponse'
declare const self: ServiceWorkerGlobalScope
clientsClaim()
// Precache all of the assets generated by your build process.
// Their URLs are injected into the manifest variable below.
// This variable must be present somewhere in your service worker file,
// even if you decide not to use precaching. See https://cra.link/PWA
precacheAndRoute(self.__WB_MANIFEST)
// This allows the web app to trigger skipWaiting via
// registration.waiting.postMessage({type: 'SKIP_WAITING'})
self.addEventListener('message', (event) => {
if (event.data && event.data.type === 'SKIP_WAITING') {
self.skipWaiting()
}
})
const sendDuration = async (clientId: string, response:Response) => {
const client = await self.clients.get(clientId)
if (!client) return
const text = await response.text()
const totalDuration = manifestParser(text)
client.postMessage({ duration: totalDuration })
}
// Any other custom service worker logic can go here.
self.addEventListener('fetch', async (event) => {
const regex = /m3u8/g
if (regex.test(event.request.url)) {
const getPlaylists = async () => {
const response = await fetch(event.request)
sendDuration(event.clientId, response.clone())
return response
}
event.respondWith(getPlaylists())
}
})

@ -1,4 +1,3 @@
/* eslint-disable */
// This optional code is used to register a service worker.
// register() is not called by default.
@ -9,16 +8,14 @@
// resources are updated in the background.
// To learn more about the benefits of this model and instructions on how to
// opt-in, read https://bit.ly/CRA-PWA
// opt-in, read https://cra.link/PWA
/* eslint-disable no-console */
const isLocalhost = Boolean(
window.location.hostname === 'localhost' ||
window.location.hostname === 'localhost'
// [::1] is the IPv6 localhost address.
window.location.hostname === '[::1]' ||
|| window.location.hostname === '[::1]'
// 127.0.0.0/8 are considered localhost for IPv4.
window.location.hostname.match(
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
)
|| window.location.hostname.match(/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/),
)
type Config = {
@ -27,16 +24,13 @@ type Config = {
}
export function register(config?: Config) {
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
if ('serviceWorker' in navigator) {
// The URL constructor is available in all browsers that support SW.
const publicUrl = new URL(
process.env.PUBLIC_URL,
window.location.href
)
const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href)
if (publicUrl.origin !== window.location.origin) {
// Our service worker won't work if PUBLIC_URL is on a different origin
// from what our page is served on. This might happen if a CDN is used to
// serve assets see https://github.com/facebook/create-react-app/issues/2374
// serve assets; see https://github.com/facebook/create-react-app/issues/2374
return
}
@ -51,8 +45,8 @@ export function register(config?: Config) {
// service worker/PWA documentation.
navigator.serviceWorker.ready.then(() => {
console.log(
'This web app is being served cache-first by a service ' +
'worker. To learn more, visit https://bit.ly/CRA-PWA'
'This web app is being served cache-first by a service '
+ 'worker. To learn more, visit https://cra.link/PWA',
)
})
} else {
@ -62,11 +56,11 @@ export function register(config?: Config) {
})
}
}
/* eslint no-param-reassign: "error" */
function registerValidSW(swUrl: string, config?: Config) {
navigator.serviceWorker
.register(swUrl)
.then(registration => {
.then((registration) => {
registration.onupdatefound = () => {
const installingWorker = registration.installing
if (installingWorker == null) {
@ -79,8 +73,8 @@ function registerValidSW(swUrl: string, config?: Config) {
// but the previous service worker will still serve the older
// content until all client tabs are closed.
console.log(
'New content is available and will be used when all ' +
'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
'New content is available and will be used when all '
+ 'tabs for this page are closed. See https://cra.link/PWA.',
)
// Execute callback
@ -102,7 +96,7 @@ function registerValidSW(swUrl: string, config?: Config) {
}
}
})
.catch(error => {
.catch((error) => {
console.error('Error during service worker registration:', error)
})
}
@ -110,17 +104,17 @@ function registerValidSW(swUrl: string, config?: Config) {
function checkValidServiceWorker(swUrl: string, config?: Config) {
// Check if the service worker can be found. If it can't reload the page.
fetch(swUrl, {
headers: { 'Service-Worker': 'script' }
headers: { 'Service-Worker': 'script' },
})
.then(response => {
.then((response) => {
// Ensure service worker exists, and that we really are getting a JS file.
const contentType = response.headers.get('content-type')
if (
response.status === 404 ||
(contentType != null && contentType.indexOf('javascript') === -1)
response.status === 404
|| (contentType != null && contentType.indexOf('javascript') === -1)
) {
// No service worker found. Probably a different app. Reload the page.
navigator.serviceWorker.ready.then(registration => {
navigator.serviceWorker.ready.then((registration) => {
registration.unregister().then(() => {
window.location.reload()
})
@ -131,19 +125,17 @@ function checkValidServiceWorker(swUrl: string, config?: Config) {
}
})
.catch(() => {
console.log(
'No internet connection found. App is running in offline mode.'
)
console.log('No internet connection found. App is running in offline mode.')
})
}
export function unregister() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready
.then(registration => {
.then((registration) => {
registration.unregister()
})
.catch(error => {
.catch((error) => {
console.error(error.message)
})
}

@ -0,0 +1,70 @@
declare module 'm3u8-parser' {
export const Parser = any
export interface Manifest {
allowCache: boolean,
custom: {},
dateTimeObject: Date,
dateTimeString: string,
discontinuitySequence: number,
discontinuityStarts: [number],
endList: boolean,
mediaGroups: {
AUDIO: {
'GROUP-ID': {
NAME: {
autoselect: boolean,
characteristics: string,
default: boolean,
forced: boolean,
instreamId: string,
language: string,
uri: string,
},
},
},
'CLOSED-CAPTIONS': {},
SUBTITLES: {},
VIDEO: {},
},
mediaSequence: number,
playlistType: string,
playlists: [
{
Manifest,
attributes: {},
}
],
segments: [
{
attributes: {},
byterange: {
length: number,
offset: number,
},
'cue-in': string,
'cue-out': string,
'cue-out-cont': string,
custom: {},
discontinuity: number,
duration: number,
key: {
iv: string,
method: string,
uri: string,
},
map: {
byterange: {
length: number,
offset: number,
},
uri: string,
},
timeline: number,
uri: string,
}
],
targetDuration: number,
totalDuration: number,
}
}

@ -19,10 +19,7 @@
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"noUnusedLocals": true,
"noFallthroughCasesInSwitch": true
},
"include": [
"src"
]
"include": ["src"]
}

Loading…
Cancel
Save