[
  {
    "path": ".eslintrc.js",
    "content": "// https://robertcooper.me/post/using-eslint-and-prettier-in-a-typescript-project\nmodule.exports = {\n  parser: '@typescript-eslint/parser',\n  extends: [\n    'plugin:react/recommended',\n    'plugin:@typescript-eslint/recommended',\n    'plugin:@typescript-eslint/eslint-recommended',\n    'plugin:prettier/recommended',\n  ],\n  parserOptions: {\n    ecmaVersion: 2020,\n    sourceType: 'module',\n    ecmaFeatures: {\n      jsx: true,\n    },\n  },\n  rules: {\n    '@typescript-eslint/semi': ['error', 'never'],\n    '@typescript-eslint/no-use-before-define': [\n      'error',\n      { functions: false, classes: false, variables: false, typedefs: true },\n    ],\n    '@typescript-eslint/explicit-function-return-type': 0,\n    '@typescript-eslint/prefer-interface': 0,\n    '@typescript-eslint/interface-name-prefix': 0,\n    '@typescript-eslint/no-non-null-assertion': 0,\n    '@typescript-eslint/explicit-module-boundary-types': 0,\n    '@typescript-eslint/camelcase': 0,\n    '@typescript-eslint/ban-ts-ignore': 0,\n    '@typescript-eslint/explicit-member-accessibility': 0,\n    semi: 'off',\n    eqeqeq: 'error',\n    'arrow-parens': ['error', 'as-needed'],\n    'no-use-before-define': ['error', { functions: false, classes: false, variables: false }],\n    'prefer-arrow-callback': 1,\n    'no-use-before-define': 0,\n    'max-len': ['warn', { code: 100, ignoreComments: true, ignorePattern: '^import .*' }],\n    'new-parens': 'error',\n    'no-bitwise': 'error',\n    'no-console': ['warn', { allow: ['warn', 'info', 'error'] }],\n    'no-caller': 'error',\n    'no-multiple-empty-lines': ['error', { max: 2, maxEOF: 1, maxBOF: 0 }],\n    'quote-props': ['error', 'as-needed'],\n    'sort-imports-es6-autofix/sort-imports-es6': [\n      2,\n      {\n        ignoreCase: false,\n        ignoreMemberSort: false,\n        memberSyntaxSortOrder: ['none', 'all', 'multiple', 'single'],\n      },\n    ],\n    'no-irregular-whitespace': 'warn',\n    'react/jsx-uses-react': 'off',\n    'react/react-in-jsx-scope': 'off',\n    'react/prop-types': 'off',\n  },\n  plugins: ['sort-imports-es6-autofix', 'react-hooks'],\n  settings: {\n    react: {\n      version: 'detect',\n    },\n  },\n}\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "github: ihmpavel\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: bug\nassignees: ''\n\n---\n\n**Describe the bug**\nA clear and concise description of what the bug is.\n\n**To Reproduce**\nSteps to reproduce the behavior:\n1. Go to '...'\n2. Click on '....'\n3. Scroll down to '....'\n4. See error\n\nLink to reproduction on (Snack)[https://snack.expo.io/]\n\n**Expected behavior**\nA clear and concise description of what you expected to happen.\n\n**Additional information:**\n - Type: [e.g. Simulator/Expo/Snack/Real device]\n - Device: [e.g. iPhone X]\n - OS: [e.g. iOS8.1]\n - Package version [e.g. 1.0.0]\n - Expo version (in `app.json`)\n - Expo CLI version\n\n**Additional context**\nAdd any other context about the problem here.\n\n**Screenshots** (if applicable)\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Is your feature request related to a problem? Please describe.**\nA clear and concise description of what the problem is. Ex. I'm always frustrated when [...]\n\n**Describe the solution you'd like**\nA clear and concise description of what you want to happen.\n\n**Describe alternatives you've considered**\nA clear and concise description of any alternative solutions or features you've considered.\n\n**Additional context**\nAdd any other context or screenshots about the feature request here.\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n- package-ecosystem: npm\n  directory: \"/\"\n  schedule:\n    interval: weekly\n    time: \"04:00\"\n  open-pull-requests-limit: 10\n"
  },
  {
    "path": ".gitignore",
    "content": "node_modules/**/*\n"
  },
  {
    "path": ".npmignore",
    "content": "lib/\nexample-app/\n.vscode/\n.github/\n.eslintrc.js\n.prettierrc.js\ntsconfig.json\njest.config.js\n"
  },
  {
    "path": ".prettierrc.js",
    "content": "module.exports = {\n  semi: false,\n  useTabs: false,\n  trailingComma: \"es5\",\n  singleQuote: true,\n  printWidth: 100,\n  tabWidth: 2,\n  arrowParens: \"avoid\",\n  jsxSingleQuote: true\n};"
  },
  {
    "path": ".vscode/settings.json",
    "content": "{\n    \"typescript.tsdk\": \"node_modules\\\\typescript\\\\lib\",\n    \"eslint.autoFixOnSave\": true,\n    \"eslint.validate\": [\n        \"javascript\",\n        \"javascriptreact\",\n        {\n            \"language\": \"typescript\",\n            \"autoFix\": true\n        },\n        {\n            \"language\": \"typescriptreact\",\n            \"autoFix\": true\n        }\n    ],\n    \"editor.formatOnSave\": true,\n    \"[javascript]\": {\n        \"editor.formatOnSave\": false,\n    },\n    \"[javascriptreact]\": {\n        \"editor.formatOnSave\": false,\n    },\n    \"[typescript]\": {\n        \"editor.formatOnSave\": false,\n    },\n    \"[typescriptreact]\": {\n        \"editor.formatOnSave\": false,\n    },\n    \"editor.codeActionsOnSave\": {\n        \"source.fixAll.eslint\": true\n    },\n}"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# ChangeLog\n\n## 2.2.0 (September 29, 2022)\n- Fix: Prevent accidental pressing of buttons in overlay header when the overlay is not visible by [@lpezzolla](https://github.com/lpezzolla) [#724](https://github.com/ihmpavel/expo-video-player/pull/724)\n- Fix: Use fullscreen icons passed in props as an alternative to package-defined icons by [@lpezzolla](https://github.com/lpezzolla) [#727](https://github.com/ihmpavel/expo-video-player/pull/727)\n\n## 2.1.0 (June 24, 2022)\n- Enhancements: Updated packages and bumped Expo SDK version\n- Enhancements: Added `mute` functionality\n\n## 2.0.4 (January 24, 2021)\n- Fix: Replay icon on iOS [#469](https://github.com/ihmpavel/expo-video-player/issues/469)\n\n## 2.0.3 (December 8, 2021)\n- Fix: Rebuild app to include `header` on top of the component instead of bottom\n\n## 2.0.2 (December 4, 2021)\n- Enhancements: Updated packages and bumped Expo SDK version\n- Enhancements: Added `autoHidePlayer` by [@hungvu193](https://github.com/hungvu193) [#506](https://github.com/ihmpavel/expo-video-player/pull/506)\n- Enhancements: Added `header` by [@Qeepsake](https://github.com/Qeepsake) [#516](https://github.com/ihmpavel/expo-video-player/pull/516)\n\n## 2.0.1 (June 27, 2021)\n- Fix: Expo Web [#433](https://github.com/ihmpavel/expo-video-player/issues/433)\n\n## 2.0.0 (June 20, 2021)\n- Rewritten, simplified\n- If you are upgrading from version `1.x`, please check [Migration guide to version 2](https://github.com/ihmpavel/expo-video-player/blob/master/migration-1x-to-2x.md)\n\n## 1.6.1 (October 10, 2020)\n- Enhancements: Updated packages and bumped Expo SDK version\n\n## 1.6.0 (August 19, 2020)\n- Fix: Renamed iosThumbImage to thumbImage\n- Enhancements: Remove deprecated Slider in favor of [community version](https://github.com/react-native-community/react-native-slider)\n- Enhancements: Added videoRef prop\n\n## 1.5.8 (May 6, 2020)\n- Enhancements: Allow disabling Slider\n\n## 1.5.7 (January 31, 2020)\n- Fix: Revert removing depracated Slider from RN Core\n\n## 1.5.6 (January 29, 2020)\n- Fix: Switch inFullscreen logic\n- Enhancements: Remove deprecated Slider\n\n## 1.5.5 (January 1, 2020)\n- Happy new Year 🎉\n- Fix: Simplify logic\n\n## 1.5.4 (December 29, 2019)\n- Fix: Building\n\n## 1.5.3 (December 29, 2019)\n- Fix: TypeScript types\n- Enhancements: Updated README\n\n## 1.5.2 (December 20, 2019)\n- Enhancements: Expo SDK 36\n- Enhancements: Updated README\n\n## 1.5.1 (September 30, 2019)\n- Fix: Play/Pause icon clicking\n\n## 1.5.0 (August 27, 2019)\n- Enhancements: Human readable debug\n- Enhancements: Renamed `isPortrait` to `inFullscreen`\n- Fix: Removed buggy internet status debug\n\n## 1.4.0 (August 27, 2019)\n- Enhancement: Added width/height props to `<Video />` [#7](https://github.com/ihmpavel/expo-video-player/issues/7)\n- Enhancement: Added `videoBackground` prop\n- Enhancement: Added more video examples\n- Enhancement: Checking internet status with hooks\n\n## 1.3.1 (August 25, 2019)\n- Fix: Do not add to npm registry Lint source files\n\n## 1.3.0 (August 25, 2019)\n- Enhancement: Added changelog\n- Enhancement: Fully rewritten, code cleanup\n- Enhancement: Rewritten `example app`, now in TS\n- Fix: Add setAudioModeAsync key [#2](https://github.com/ihmpavel/expo-video-player/issues/2)\n- Fix: Updated Expo [#3](https://github.com/ihmpavel/expo-video-player/issues/3)\n\n## 1.2.0 (April 12, 2019)\n- Enhancement: Refactored code\n\n## 1.1.1 (April 12, 2019)\n- Enhancement: Updated dependencies\n\n## 1.1.0 (April 12, 2019)\n- Fix: Crashing app\n\n## 1.0.0 (December 22, 2018)\n- Initial release 🙌🤗"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2021 @ihmpavel/expo-video-player\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE."
  },
  {
    "path": "README.md",
    "content": "# Basic info\nVideo wrappper component for Expo ecosystem built on top of the Expo's [Video component](https://docs.expo.io/versions/latest/sdk/video/). This library basically adds UI controls like in the YouTube app, which gives you the opportunity to play, pause, replay, change video position and a lot of styling options.\n\nThe package has a lot of configuration options to fit all your needs. Only `source` in `videoProps: { source: {} }` is required. Check the <a href='#props'>Props</a> table below.\n\nFor compatibility information, scroll down to <a href='#compatibility'>Compatibility</a>. The FAQ is <a href='#faq'>here</a>\n\n## ⚠️ Updating from version 1.x to 2.x\nIf you are updating from version 1.x to 2.x, there are some breaking changes in the API. Please visit [Migration guide to version 2](https://github.com/ihmpavel/expo-video-player/blob/master/migration-1x-to-2x.md) to make your transition as easy as possible. In version 2.x [@react-native-community/netinfo](https://github.com/react-native-netinfo/react-native-netinfo) has been removed.\n\n## Installation\n- Install Video Player component typing into terminal `yarn add expo-video-player` _or_ `npm install expo-video-player`\n- You also need `expo-av` and `@react-native-community/slider`. Install them with `expo-cli` (`expo install expo-av @react-native-community/slider`)\n\n## Usage\nThe showcase of some of the possibilities you can create is in the folder [example-app](https://github.com/ihmpavel/expo-video-player/blob/master/example-app). There is Fullscreen, ref, local file, custom icons, styling...\n\nMinimal code to make `VideoPlayer` working\n```\nimport { ResizeMode } from 'expo-av'\nimport VideoPlayer from 'expo-video-player'\n\n<VideoPlayer\n  videoProps={{\n    shouldPlay: true,\n    resizeMode: ResizeMode.CONTAIN,\n    // ❗ source is required https://docs.expo.io/versions/latest/sdk/video/#props\n    source: {\n      uri: 'http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4',\n    },\n  }}\n/>\n```\n\n## Props\nFor default prop values, please visit [/lib/props.tsx](https://github.com/ihmpavel/expo-video-player/blob/master/lib/props.tsx#L11)\n\n| Property | Type | Description |\n| ---- | :-------: | ----------- |\n| **videoProps** | [`VideoProps`](https://docs.expo.io/versions/latest/sdk/video/#props) | At least `source` is required |\n| **errorCallback** | (error: ErrorType) => void | Function which is fired when an error occurs |\n| **playbackCallback** | (status: AVPlaybackStatus) => void | Function which is fired every time `onPlaybackStatusUpdate` occurs |\n| **defaultControlsVisible** | `boolean` | Show controls on darker overlay when video starts playing. Default is `false` |\n| **timeVisible** | `boolean` | Show current time and final length in the bottom. Default is `true` |\n| **textStyle** | `TextStyle` | Object containing `<Text />` styling |\n| **slider** | `{ visible?: boolean } & SliderProps` | Object containing any of [@react-native-community/slider](https://github.com/callstack/react-native-slider) props. Your styling may break default layout. Also hide slider by providing `visible: false` prop. You are unable to overwrite `ref`, `value`, `onSlidingStart` and `onSlidingComplete` |\n| **activityIndicator** | `ActivityIndicatorProps` | Any values from [ActivityIndicator](https://reactnative.dev/docs/activityindicator) |\n| **animation** | `{ fadeInDuration?: number, fadeOutDuration?: number }` | Duration of animations in milliseconds |\n| **style** | `{ width?: number, height?: number, videoBackgroundColor?: ColorValue, controlsBackgroundColor?: ColorValue }` | Basic styling of `<VideoPlayer />` |\n| **icon** | `{ size?: number, color?: ColorValue, style?: TextStyle, pause?: JSX.Element, play?: JSX.Element, replay?: JSX.Element, fullscreen?: JSX.Element, exitFullscreen?: JSX.Element, mute?: JSX.Element, exitMute?: JSX.Element }` | Icon styling. Check more in the [example-app](https://github.com/ihmpavel/expo-video-player/blob/master/example-app/App.tsx) |\n| **fullscreen** | `{ enterFullscreen?: () => void, exitFullscreen?: () => void, inFullscreen?: boolean, visible?: boolean }` | Usage of `Fullscreen` mode is in the [example-app](https://github.com/ihmpavel/expo-video-player/blob/master/example-app/App.tsx#L182) |\n| **autoHidePlayer** | `boolean` | Prevent player from hiding after certain time, by setting it to `false` you need to tap the screen again to hide the player. Default is `true` |\n| **header** | `ReactNode` | Render header component same as in YouTube app. Default `undefined` |\n| **mute** | `{ enterMute?: () => void, exitMute?: () => void, isMute?: boolean, visible?: boolean }` | Usage of `mute` mode is in the [example-app](example-app/App.tsx) |\n\n## Compatibility\nLibrary version | Expo SDK version\n---- | -------\n2.1.x | >= SDK 45\n2.x.x | >= SDK 38\n1.6.x | >= SDK 38\n1.5.x | >= SDK 34\n1.4.x | >= SDK 34\n1.3.x | >= SDK 34\n1.2.x | >= SDK 33\n1.1.x | >= SDK 32\n1.x.x | >= SDK 32\n\n### CHANGELOG\nChangelog added in version 1.3.0\nRead [CHANGELOG.md](https://github.com/ihmpavel/expo-video-player/blob/master/CHANGELOG.md)\n\n### FAQ\n- **How to make fullscreen working?** Please visit [example-app](https://github.com/ihmpavel/expo-video-player/blob/master/example-app/App.tsx#L182)\n- **How to use ref?** Please visit [example-app](https://github.com/ihmpavel/expo-video-player/blob/master/example-app/App.tsx)\n- **What to do if I disconnect from the internet while playing video from remote source?** You need to stop/pause playback yourself. I highly recommend using [@react-native-community/netinfo](https://github.com/react-native-netinfo/react-native-netinfo) for this kind of stuff\n- **Do you support subtitles?** Have a look at [#1](https://github.com/ihmpavel/expo-video-player/issues/1)\n- **Can I support you?** Yes, please [Become a sponsor](https://github.com/sponsors/ihmpavel)\n\n### TODO\n- [ ] make tests\n\n#### Some articles\n - Inspired by [expo/videoplayer](https://github.com/expo/videoplayer) _(already deprecated)_\n - [Typescript default props](https://github.com/typescript-cheatsheets/react/issues/415)\n - [Creating a typescript module](https://codeburst.io/https-chidume-nnamdi-com-npm-module-in-typescript-12b3b22f0724)\n - [Creating a component for React](https://medium.com/@BrodaNoel/how-to-create-a-react-component-and-publish-it-in-npm-668ad7d363ce)\n\n\n## More packages from me\n- [all-iso-language-codes](https://github.com/ihmpavel/all-iso-language-codes) - List of ISO 639-1, 639-2T, 639-2B and 639-3 codes with translations in all available languages\n- [expo-video-player](https://github.com/ihmpavel/expo-video-player) - Customizable Video Player controls for Expo\n- [free-email-domains-list](https://github.com/ihmpavel/free-email-domains-list) - Fresh list of all free email domain providers. Can be used to check if an email address belongs to a company. Updated weekly\n"
  },
  {
    "path": "dist/constants.d.ts",
    "content": "export declare enum ControlStates {\n    Visible = \"Visible\",\n    Hidden = \"Hidden\"\n}\nexport declare enum PlaybackStates {\n    Loading = \"Loading\",\n    Playing = \"Playing\",\n    Paused = \"Paused\",\n    Buffering = \"Buffering\",\n    Error = \"Error\",\n    Ended = \"Ended\"\n}\nexport declare enum ErrorSeverity {\n    Fatal = \"Fatal\",\n    NonFatal = \"NonFatal\"\n}\nexport declare type ErrorType = {\n    type: ErrorSeverity;\n    message: string;\n    obj: Record<string, unknown>;\n};\n"
  },
  {
    "path": "dist/constants.js",
    "content": "export var ControlStates;\n(function (ControlStates) {\n    ControlStates[\"Visible\"] = \"Visible\";\n    ControlStates[\"Hidden\"] = \"Hidden\";\n})(ControlStates || (ControlStates = {}));\nexport var PlaybackStates;\n(function (PlaybackStates) {\n    PlaybackStates[\"Loading\"] = \"Loading\";\n    PlaybackStates[\"Playing\"] = \"Playing\";\n    PlaybackStates[\"Paused\"] = \"Paused\";\n    PlaybackStates[\"Buffering\"] = \"Buffering\";\n    PlaybackStates[\"Error\"] = \"Error\";\n    PlaybackStates[\"Ended\"] = \"Ended\";\n})(PlaybackStates || (PlaybackStates = {}));\nexport var ErrorSeverity;\n(function (ErrorSeverity) {\n    ErrorSeverity[\"Fatal\"] = \"Fatal\";\n    ErrorSeverity[\"NonFatal\"] = \"NonFatal\";\n})(ErrorSeverity || (ErrorSeverity = {}));\n"
  },
  {
    "path": "dist/index.d.ts",
    "content": "import { AVPlaybackStatus } from 'expo-av';\nimport { Props } from './props';\nimport React from 'react';\ndeclare const VideoPlayer: {\n    (tempProps: Props): JSX.Element;\n    defaultProps: {\n        errorCallback: (error: import(\"./constants\").ErrorType) => void;\n        playbackCallback: (status: AVPlaybackStatus) => void;\n        defaultControlsVisible: boolean;\n        timeVisible: boolean;\n        textStyle: import(\"react-native\").TextStyle;\n        slider: {\n            visible?: boolean | undefined;\n        } & import(\"@react-native-community/slider\").SliderProps;\n        activityIndicator: import(\"react-native\").ActivityIndicatorProps;\n        animation: {\n            fadeInDuration?: number | undefined;\n            fadeOutDuration?: number | undefined;\n        };\n        header: React.ReactNode;\n        style: {\n            width?: number | undefined;\n            height?: number | undefined;\n            videoBackgroundColor?: import(\"react-native\").ColorValue | undefined;\n            controlsBackgroundColor?: import(\"react-native\").ColorValue | undefined;\n        };\n        icon: {\n            size?: number | undefined;\n            color?: import(\"react-native\").ColorValue | undefined;\n            style?: import(\"react-native\").TextStyle | undefined;\n            pause?: JSX.Element | undefined;\n            play?: JSX.Element | undefined;\n            replay?: JSX.Element | undefined;\n            loading?: JSX.Element | undefined;\n            fullscreen?: JSX.Element | undefined;\n            exitFullscreen?: JSX.Element | undefined;\n            mute?: JSX.Element | undefined;\n            exitMute?: JSX.Element | undefined;\n        };\n        fullscreen: {\n            enterFullscreen?: (() => void) | undefined;\n            exitFullscreen?: (() => void) | undefined;\n            inFullscreen?: boolean | undefined;\n            visible?: boolean | undefined;\n        };\n        autoHidePlayer: boolean;\n        mute: {\n            enterMute?: (() => void) | undefined;\n            exitMute?: (() => void) | undefined;\n            isMute?: boolean | undefined;\n            visible?: boolean | undefined;\n        };\n    };\n};\nexport default VideoPlayer;\n"
  },
  {
    "path": "dist/index.js",
    "content": "import { __awaiter, __rest } from \"tslib\";\nimport { Audio, Video } from 'expo-av';\nimport { ActivityIndicator, Animated, StyleSheet, Text, TouchableWithoutFeedback, View, } from 'react-native';\nimport { ControlStates, ErrorSeverity, PlaybackStates } from './constants';\nimport { ErrorMessage, TouchableButton, deepMerge, getMinutesSecondsFromMilliseconds, styles, } from './utils';\nimport { MaterialIcons } from '@expo/vector-icons';\nimport { defaultProps } from './props';\nimport { useEffect, useRef, useState } from 'react';\nimport React from 'react';\nimport Slider from '@react-native-community/slider';\nconst VideoPlayer = (tempProps) => {\n    const props = deepMerge(defaultProps, tempProps);\n    let playbackInstance = null;\n    let controlsTimer = null;\n    let initialShow = props.defaultControlsVisible;\n    const header = props.header;\n    const [errorMessage, setErrorMessage] = useState('');\n    const controlsOpacity = useRef(new Animated.Value(props.defaultControlsVisible ? 1 : 0)).current;\n    const [controlsState, setControlsState] = useState(props.defaultControlsVisible ? ControlStates.Visible : ControlStates.Hidden);\n    const [playbackInstanceInfo, setPlaybackInstanceInfo] = useState({\n        position: 0,\n        duration: 0,\n        state: props.videoProps.source ? PlaybackStates.Loading : PlaybackStates.Error,\n    });\n    // We need to extract ref, because of misstypes in <Slider />\n    // eslint-disable-next-line @typescript-eslint/no-unused-vars\n    const _a = props.slider, { ref: sliderRef } = _a, sliderProps = __rest(_a, [\"ref\"]);\n    const screenRatio = props.style.width / props.style.height;\n    let videoHeight = props.style.height;\n    let videoWidth = videoHeight * screenRatio;\n    if (videoWidth > props.style.width) {\n        videoWidth = props.style.width;\n        videoHeight = videoWidth / screenRatio;\n    }\n    useEffect(() => {\n        setAudio();\n        return () => {\n            if (playbackInstance) {\n                playbackInstance.setStatusAsync({\n                    shouldPlay: false,\n                });\n            }\n        };\n    }, []);\n    useEffect(() => {\n        if (!props.videoProps.source) {\n            console.error('[VideoPlayer] `Source` is a required in `videoProps`. ' +\n                'Check https://docs.expo.io/versions/latest/sdk/video/#usage');\n            setErrorMessage('`Source` is a required in `videoProps`');\n            setPlaybackInstanceInfo(Object.assign(Object.assign({}, playbackInstanceInfo), { state: PlaybackStates.Error }));\n        }\n        else {\n            setPlaybackInstanceInfo(Object.assign(Object.assign({}, playbackInstanceInfo), { state: PlaybackStates.Playing }));\n        }\n    }, [props.videoProps.source]);\n    const hideAnimation = () => {\n        Animated.timing(controlsOpacity, {\n            toValue: 0,\n            duration: props.animation.fadeOutDuration,\n            useNativeDriver: true,\n        }).start(({ finished }) => {\n            if (finished) {\n                setControlsState(ControlStates.Hidden);\n            }\n        });\n    };\n    const animationToggle = () => {\n        if (controlsState === ControlStates.Hidden) {\n            Animated.timing(controlsOpacity, {\n                toValue: 1,\n                duration: props.animation.fadeInDuration,\n                useNativeDriver: true,\n            }).start(({ finished }) => {\n                if (finished) {\n                    setControlsState(ControlStates.Visible);\n                }\n            });\n        }\n        else if (controlsState === ControlStates.Visible) {\n            hideAnimation();\n        }\n        if (controlsTimer === null && props.autoHidePlayer) {\n            controlsTimer = setTimeout(() => {\n                if (playbackInstanceInfo.state === PlaybackStates.Playing &&\n                    controlsState === ControlStates.Hidden) {\n                    hideAnimation();\n                }\n                if (controlsTimer) {\n                    clearTimeout(controlsTimer);\n                }\n                controlsTimer = null;\n            }, 2000);\n        }\n    };\n    // Set audio mode to play even in silent mode (like the YouTube app)\n    const setAudio = () => __awaiter(void 0, void 0, void 0, function* () {\n        try {\n            yield Audio.setAudioModeAsync({\n                playsInSilentModeIOS: true,\n            });\n        }\n        catch (e) {\n            props.errorCallback({\n                type: ErrorSeverity.NonFatal,\n                message: 'Audio.setAudioModeAsync',\n                obj: e,\n            });\n        }\n    });\n    const updatePlaybackCallback = (status) => {\n        props.playbackCallback(status);\n        if (status.isLoaded) {\n            setPlaybackInstanceInfo(Object.assign(Object.assign({}, playbackInstanceInfo), { position: status.positionMillis, duration: status.durationMillis || 0, state: status.positionMillis === status.durationMillis\n                    ? PlaybackStates.Ended\n                    : status.isBuffering\n                        ? PlaybackStates.Buffering\n                        : status.shouldPlay\n                            ? PlaybackStates.Playing\n                            : PlaybackStates.Paused }));\n            if ((status.didJustFinish && controlsState === ControlStates.Hidden) ||\n                (status.isBuffering && controlsState === ControlStates.Hidden && initialShow)) {\n                animationToggle();\n                initialShow = false;\n            }\n        }\n        else {\n            if (status.isLoaded === false && status.error) {\n                const errorMsg = `Encountered a fatal error during playback: ${status.error}`;\n                setErrorMessage(errorMsg);\n                props.errorCallback({ type: ErrorSeverity.Fatal, message: errorMsg, obj: {} });\n            }\n        }\n    };\n    const togglePlay = () => __awaiter(void 0, void 0, void 0, function* () {\n        if (controlsState === ControlStates.Hidden) {\n            return;\n        }\n        const shouldPlay = playbackInstanceInfo.state !== PlaybackStates.Playing;\n        if (playbackInstance !== null) {\n            yield playbackInstance.setStatusAsync(Object.assign({ shouldPlay }, (playbackInstanceInfo.state === PlaybackStates.Ended && { positionMillis: 0 })));\n            setPlaybackInstanceInfo(Object.assign(Object.assign({}, playbackInstanceInfo), { state: playbackInstanceInfo.state === PlaybackStates.Playing\n                    ? PlaybackStates.Paused\n                    : PlaybackStates.Playing }));\n            if (shouldPlay) {\n                animationToggle();\n            }\n        }\n    });\n    if (playbackInstanceInfo.state === PlaybackStates.Error) {\n        return (<View style={{\n                backgroundColor: props.style.videoBackgroundColor,\n                width: videoWidth,\n                height: videoHeight,\n            }}>\n        <ErrorMessage style={props.textStyle} message={errorMessage}/>\n      </View>);\n    }\n    if (playbackInstanceInfo.state === PlaybackStates.Loading) {\n        return (<View style={{\n                backgroundColor: props.style.controlsBackgroundColor,\n                width: videoWidth,\n                height: videoHeight,\n                justifyContent: 'center',\n            }}>\n        {props.icon.loading || <ActivityIndicator {...props.activityIndicator}/>}\n      </View>);\n    }\n    return (<View style={{\n            backgroundColor: props.style.videoBackgroundColor,\n            width: videoWidth,\n            height: videoHeight,\n            maxWidth: '100%',\n        }}>\n      <Video style={styles.videoWrapper} {...props.videoProps} ref={component => {\n            playbackInstance = component;\n            if (props.videoProps.ref) {\n                props.videoProps.ref.current = component;\n            }\n        }} onPlaybackStatusUpdate={updatePlaybackCallback}/>\n\n      <Animated.View pointerEvents={controlsState === ControlStates.Visible ? 'auto' : 'none'} style={[\n            styles.topInfoWrapper,\n            {\n                opacity: controlsOpacity,\n            },\n        ]}>\n        {header}\n      </Animated.View>\n\n      <TouchableWithoutFeedback onPress={animationToggle}>\n        <Animated.View style={Object.assign(Object.assign({}, StyleSheet.absoluteFillObject), { opacity: controlsOpacity, justifyContent: 'center', alignItems: 'center' })}>\n          <View style={Object.assign(Object.assign({}, StyleSheet.absoluteFillObject), { backgroundColor: props.style.controlsBackgroundColor, opacity: 0.5 })}/>\n          <View pointerEvents={controlsState === ControlStates.Visible ? 'auto' : 'none'}>\n            <View style={styles.iconWrapper}>\n              <TouchableButton onPress={togglePlay}>\n                <View>\n                  {playbackInstanceInfo.state === PlaybackStates.Buffering &&\n            (props.icon.loading || <ActivityIndicator {...props.activityIndicator}/>)}\n                  {playbackInstanceInfo.state === PlaybackStates.Playing && props.icon.pause}\n                  {playbackInstanceInfo.state === PlaybackStates.Paused && props.icon.play}\n                  {playbackInstanceInfo.state === PlaybackStates.Ended && props.icon.replay}\n                  {((playbackInstanceInfo.state === PlaybackStates.Ended && !props.icon.replay) ||\n            (playbackInstanceInfo.state === PlaybackStates.Playing && !props.icon.pause) ||\n            (playbackInstanceInfo.state === PlaybackStates.Paused &&\n                !props.icon.pause)) && (<MaterialIcons name={playbackInstanceInfo.state === PlaybackStates.Playing\n                ? 'pause'\n                : playbackInstanceInfo.state === PlaybackStates.Paused\n                    ? 'play-arrow'\n                    : 'replay'} style={props.icon.style} size={props.icon.size} color={props.icon.color}/>)}\n                </View>\n              </TouchableButton>\n            </View>\n          </View>\n        </Animated.View>\n      </TouchableWithoutFeedback>\n\n      <Animated.View pointerEvents={controlsState === ControlStates.Visible ? 'auto' : 'none'} style={[\n            styles.bottomInfoWrapper,\n            {\n                opacity: controlsOpacity,\n            },\n        ]}>\n        {props.timeVisible && (<Text style={[props.textStyle, styles.timeLeft]}>\n            {getMinutesSecondsFromMilliseconds(playbackInstanceInfo.position)}\n          </Text>)}\n        {props.slider.visible && (<Slider {...sliderProps} style={[styles.slider, props.slider.style]} value={playbackInstanceInfo.duration\n                ? playbackInstanceInfo.position / playbackInstanceInfo.duration\n                : 0} onSlidingStart={() => {\n                if (playbackInstanceInfo.state === PlaybackStates.Playing) {\n                    togglePlay();\n                    setPlaybackInstanceInfo(Object.assign(Object.assign({}, playbackInstanceInfo), { state: PlaybackStates.Paused }));\n                }\n            }} onSlidingComplete={(e) => __awaiter(void 0, void 0, void 0, function* () {\n                const position = e * playbackInstanceInfo.duration;\n                if (playbackInstance) {\n                    yield playbackInstance.setStatusAsync({\n                        positionMillis: position,\n                        shouldPlay: true,\n                    });\n                }\n                setPlaybackInstanceInfo(Object.assign(Object.assign({}, playbackInstanceInfo), { position }));\n            })}/>)}\n        {props.timeVisible && (<Text style={[props.textStyle, styles.timeRight]}>\n            {getMinutesSecondsFromMilliseconds(playbackInstanceInfo.duration)}\n          </Text>)}\n        {props.mute.visible && (<TouchableButton onPress={() => { var _a, _b, _c, _d; return (props.mute.isMute ? (_b = (_a = props.mute).exitMute) === null || _b === void 0 ? void 0 : _b.call(_a) : (_d = (_c = props.mute).enterMute) === null || _d === void 0 ? void 0 : _d.call(_c)); }}>\n            <View>\n              {props.icon.mute}\n              {props.icon.exitMute}\n              {((!props.icon.mute && props.mute.isMute) ||\n                (!props.icon.exitMute && !props.mute.isMute)) && (<MaterialIcons name={props.mute.isMute ? 'volume-up' : 'volume-off'} style={props.icon.style} size={props.icon.size / 2} color={props.icon.color}/>)}\n            </View>\n          </TouchableButton>)}\n        {props.fullscreen.visible && (<TouchableButton onPress={() => props.fullscreen.inFullscreen\n                ? props.fullscreen.exitFullscreen()\n                : props.fullscreen.enterFullscreen()}>\n            <View>\n              {!props.fullscreen.inFullscreen && props.icon.fullscreen}\n              {props.fullscreen.inFullscreen && props.icon.exitFullscreen}\n              {((!props.icon.fullscreen && !props.fullscreen.inFullscreen) ||\n                (!props.icon.exitFullscreen && props.fullscreen.inFullscreen)) && (<MaterialIcons name={props.fullscreen.inFullscreen ? 'fullscreen-exit' : 'fullscreen'} style={props.icon.style} size={props.icon.size / 2} color={props.icon.color}/>)}\n            </View>\n          </TouchableButton>)}\n      </Animated.View>\n    </View>);\n};\nVideoPlayer.defaultProps = defaultProps;\nexport default VideoPlayer;\n"
  },
  {
    "path": "dist/props.d.ts",
    "content": "import { AVPlaybackStatus, Video, VideoProps } from 'expo-av';\nimport { ActivityIndicatorProps, TextStyle } from 'react-native';\nimport { ColorValue } from 'react-native';\nimport { ErrorType } from './constants';\nimport { MutableRefObject, ReactNode } from 'react';\nimport { SliderProps } from '@react-native-community/slider';\nexport declare type Props = RequiredProps & DefaultProps;\nexport declare const defaultProps: DefaultProps;\ndeclare type RequiredProps = {\n    videoProps: VideoProps & {\n        ref?: MutableRefObject<Video>;\n    };\n};\ndeclare type DefaultProps = {\n    errorCallback: (error: ErrorType) => void;\n    playbackCallback: (status: AVPlaybackStatus) => void;\n    defaultControlsVisible: boolean;\n    timeVisible: boolean;\n    textStyle: TextStyle;\n    slider: {\n        visible?: boolean;\n    } & SliderProps;\n    activityIndicator: ActivityIndicatorProps;\n    animation: {\n        fadeInDuration?: number;\n        fadeOutDuration?: number;\n    };\n    header: ReactNode;\n    style: {\n        width?: number;\n        height?: number;\n        videoBackgroundColor?: ColorValue;\n        controlsBackgroundColor?: ColorValue;\n    };\n    icon: {\n        size?: number;\n        color?: ColorValue;\n        style?: TextStyle;\n        pause?: JSX.Element;\n        play?: JSX.Element;\n        replay?: JSX.Element;\n        loading?: JSX.Element;\n        fullscreen?: JSX.Element;\n        exitFullscreen?: JSX.Element;\n        mute?: JSX.Element;\n        exitMute?: JSX.Element;\n    };\n    fullscreen: {\n        enterFullscreen?: () => void;\n        exitFullscreen?: () => void;\n        inFullscreen?: boolean;\n        visible?: boolean;\n    };\n    autoHidePlayer: boolean;\n    mute: {\n        enterMute?: () => void;\n        exitMute?: () => void;\n        isMute?: boolean;\n        visible?: boolean;\n    };\n};\nexport {};\n"
  },
  {
    "path": "dist/props.js",
    "content": "import { Dimensions, Platform } from 'react-native';\nexport const defaultProps = {\n    errorCallback: error => console.error(`[VideoPlayer] ${error.type} Error - ${error.message}: ${error.obj}`),\n    // eslint-disable-next-line @typescript-eslint/no-empty-function\n    playbackCallback: () => { },\n    defaultControlsVisible: false,\n    timeVisible: true,\n    slider: {\n        visible: true,\n    },\n    textStyle: {\n        color: '#FFF',\n        fontSize: 12,\n        textAlign: 'center',\n    },\n    activityIndicator: {\n        size: 'large',\n        color: '#999',\n    },\n    animation: {\n        fadeInDuration: 300,\n        fadeOutDuration: 300,\n    },\n    style: {\n        width: Platform.OS === 'web' ? '100%' : Dimensions.get('window').width,\n        height: Dimensions.get('window').height,\n        videoBackgroundColor: '#000',\n        controlsBackgroundColor: '#000',\n    },\n    icon: {\n        size: 48,\n        color: '#FFF',\n        style: {\n            padding: 2,\n        },\n    },\n    fullscreen: {\n        enterFullscreen: () => \n        // eslint-disable-next-line no-console\n        console.log('[VideoPlayer] - missing `enterFullscreen` function in `fullscreen` prop'),\n        exitFullscreen: () => \n        // eslint-disable-next-line no-console\n        console.log('[VideoPlayer] - missing `exitFullscreen` function in `fullscreen` prop'),\n        inFullscreen: false,\n        visible: true,\n    },\n    autoHidePlayer: true,\n    header: undefined,\n    mute: {\n        enterMute: () => \n        // eslint-disable-next-line no-console\n        console.log('[VideoPlayer] - missing `enterMute` function in `mute` prop'),\n        exitMute: () => \n        // eslint-disable-next-line no-console\n        console.log('[VideoPlayer] - missing `exitMute` function in `mute` prop'),\n        isMute: false,\n        visible: false,\n    },\n};\n"
  },
  {
    "path": "dist/utils.d.ts",
    "content": "import { TextStyle, TouchableNativeFeedbackProps, TouchableOpacityProps } from 'react-native';\nimport React from 'react';\nexport declare const ErrorMessage: ({ message, style }: {\n    message: string;\n    style: TextStyle;\n}) => JSX.Element;\nexport declare const getMinutesSecondsFromMilliseconds: (ms: number) => string;\ndeclare type ButtonProps = (TouchableNativeFeedbackProps | TouchableOpacityProps) & {\n    children: React.ReactNode;\n};\nexport declare const TouchableButton: (props: ButtonProps) => JSX.Element;\nexport declare const deepMerge: (target: {\n    [x: string]: any;\n}, source: {\n    [x: string]: any;\n}) => {\n    [x: string]: any;\n};\nexport declare const styles: {\n    errorWrapper: {\n        paddingHorizontal: number;\n        justifyContent: \"center\";\n        position: \"absolute\";\n        left: 0;\n        right: 0;\n        top: 0;\n        bottom: 0;\n    };\n    videoWrapper: {\n        flex: number;\n        justifyContent: \"center\";\n    };\n    iconWrapper: {\n        borderRadius: number;\n        overflow: \"hidden\";\n        padding: number;\n    };\n    bottomInfoWrapper: {\n        position: \"absolute\";\n        flexDirection: \"row\";\n        alignItems: \"center\";\n        justifyContent: \"space-between\";\n        flex: number;\n        bottom: number;\n        left: number;\n        right: number;\n    };\n    topInfoWrapper: {\n        position: \"absolute\";\n        flexDirection: \"row\";\n        alignItems: \"center\";\n        justifyContent: \"space-between\";\n        flex: number;\n        top: number;\n        left: number;\n        right: number;\n        zIndex: number;\n    };\n    timeLeft: {\n        backgroundColor: string;\n        marginLeft: number;\n    };\n    timeRight: {\n        backgroundColor: string;\n        marginRight: number;\n    };\n    slider: {\n        flex: number;\n        paddingHorizontal: number;\n    };\n};\nexport {};\n"
  },
  {
    "path": "dist/utils.js",
    "content": "import { Platform, StyleSheet, Text, TouchableNativeFeedback, TouchableOpacity, View, } from 'react-native';\nimport React from 'react';\nexport const ErrorMessage = ({ message, style }) => (<View style={styles.errorWrapper}>\n    <Text style={style}>{message}</Text>\n  </View>);\nexport const getMinutesSecondsFromMilliseconds = (ms) => {\n    const totalSeconds = ms / 1000;\n    const seconds = String(Math.floor(totalSeconds % 60));\n    const minutes = String(Math.floor(totalSeconds / 60));\n    return minutes.padStart(1, '0') + ':' + seconds.padStart(2, '0');\n};\nexport const TouchableButton = (props) => Platform.OS === 'android' ? (<TouchableNativeFeedback background={TouchableNativeFeedback.Ripple('white', true)} {...props}/>) : (<TouchableOpacity {...props}/>);\n// https://gist.github.com/ahtcx/0cd94e62691f539160b32ecda18af3d6#gistcomment-3585151\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport const deepMerge = (target, source) => {\n    const result = Object.assign(Object.assign({}, target), source);\n    const keys = Object.keys(result);\n    for (const key of keys) {\n        const tprop = target[key];\n        const sprop = source[key];\n        if (typeof tprop === 'object' && typeof sprop === 'object') {\n            result[key] = deepMerge(tprop, sprop);\n        }\n    }\n    return result;\n};\nexport const styles = StyleSheet.create({\n    errorWrapper: Object.assign(Object.assign({}, StyleSheet.absoluteFillObject), { paddingHorizontal: 20, justifyContent: 'center' }),\n    videoWrapper: {\n        flex: 1,\n        justifyContent: 'center',\n    },\n    iconWrapper: {\n        borderRadius: 100,\n        overflow: 'hidden',\n        padding: 10,\n    },\n    bottomInfoWrapper: {\n        position: 'absolute',\n        flexDirection: 'row',\n        alignItems: 'center',\n        justifyContent: 'space-between',\n        flex: 1,\n        bottom: 0,\n        left: 0,\n        right: 0,\n    },\n    topInfoWrapper: {\n        position: 'absolute',\n        flexDirection: 'row',\n        alignItems: 'center',\n        justifyContent: 'space-between',\n        flex: 1,\n        top: 0,\n        left: 0,\n        right: 0,\n        zIndex: 999,\n    },\n    timeLeft: { backgroundColor: 'transparent', marginLeft: 5 },\n    timeRight: { backgroundColor: 'transparent', marginRight: 5 },\n    slider: { flex: 1, paddingHorizontal: 10 },\n});\n"
  },
  {
    "path": "example-app/.expo-shared/assets.json",
    "content": "{\n  \"f9155ac790fd02fadcdeca367b02581c04a353aa6d5aa84409a59f6804c87acd\": true,\n  \"89ed26367cdb9b771858e026f2eb95bfdb90e5ae943e716575327ec325f39c44\": true\n}"
  },
  {
    "path": "example-app/.gitignore",
    "content": "node_modules/**/*\n.expo/*\nnpm-debug.*\n*.jks\n*.p8\n*.p12\n*.key\n*.mobileprovision\n*.orig.*\nweb-build/\nweb-report/\n\n# macOS\n.DS_Store\n"
  },
  {
    "path": "example-app/App.tsx",
    "content": "import * as ScreenOrientation from 'expo-screen-orientation'\nimport { Dimensions, ScrollView, StyleSheet, Text } from 'react-native'\nimport { ResizeMode } from 'expo-av'\nimport { setStatusBarHidden } from 'expo-status-bar'\nimport React, { useRef, useState } from 'react'\nimport VideoPlayer from 'expo-video-player'\n\nconst App = () => {\n  const [inFullscreen, setInFullsreen] = useState(false)\n  const [inFullscreen2, setInFullsreen2] = useState(false)\n  const [isMute, setIsMute] = useState(false)\n  const refVideo = useRef(null)\n  const refVideo2 = useRef(null)\n  const refScrollView = useRef(null)\n\n  return (\n    <ScrollView\n      scrollEnabled={!inFullscreen2}\n      ref={refScrollView}\n      onContentSizeChange={() => {\n        if (inFullscreen2) {\n          refScrollView.current.scrollToEnd({ animated: true })\n        }\n      }}\n      style={styles.container}\n      contentContainerStyle={styles.contentContainer}\n    >\n      <Text style={[styles.text, { fontWeight: 'bold', textTransform: 'uppercase' }]}>\n        Examples\n      </Text>\n      {/* ShouldPlay (autoplay) is true only in the first example */}\n      <Text style={styles.text}>Basic</Text>\n      <VideoPlayer\n        videoProps={{\n          shouldPlay: true,\n          resizeMode: ResizeMode.CONTAIN,\n          source: {\n            uri: 'http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4',\n          },\n        }}\n      />\n\n      <Text style={styles.text}>Local file</Text>\n      <VideoPlayer\n        videoProps={{\n          shouldPlay: false,\n          resizeMode: ResizeMode.CONTAIN,\n          source: require('./local.mp4'),\n        }}\n        style={{ height: 160 }}\n      />\n\n      <Text style={styles.text}>Only video without controls</Text>\n      <VideoPlayer\n        videoProps={{\n          shouldPlay: false,\n          resizeMode: ResizeMode.CONTAIN,\n          source: {\n            uri: 'http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4',\n          },\n        }}\n        slider={{\n          visible: false,\n        }}\n        fullscreen={{\n          visible: false,\n        }}\n        timeVisible={false}\n        style={{ height: 160 }}\n      />\n\n      <Text style={styles.text}>Some styling</Text>\n      <VideoPlayer\n        videoProps={{\n          shouldPlay: false,\n          resizeMode: ResizeMode.CONTAIN,\n          source: {\n            uri: 'http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4',\n          },\n        }}\n        style={{\n          videoBackgroundColor: 'transparent',\n          controlsBackgroundColor: 'red',\n          height: 200,\n        }}\n      />\n\n      <Text style={styles.text}>With custom icons</Text>\n      <VideoPlayer\n        videoProps={{\n          shouldPlay: false,\n          resizeMode: ResizeMode.CONTAIN,\n          source: {\n            uri: 'http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4',\n          },\n        }}\n        icon={{\n          play: <Text style={{ color: '#FFF' }}>PLAY</Text>,\n          pause: <Text style={{ color: '#FFF' }}>PAUSE</Text>,\n        }}\n        style={{ height: 160 }}\n      />\n\n      <Text style={styles.text}>With some more styling</Text>\n      <VideoPlayer\n        videoProps={{\n          shouldPlay: false,\n          resizeMode: ResizeMode.CONTAIN,\n          source: {\n            uri: 'http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4',\n          },\n        }}\n        style={{\n          height: 160,\n          width: 160,\n          videoBackgroundColor: 'yellow',\n          controlsBackgroundColor: 'blue',\n        }}\n      />\n\n      <Text style={styles.text}>With Mute</Text>\n      <VideoPlayer\n        videoProps={{\n          shouldPlay: false,\n          resizeMode: ResizeMode.CONTAIN,\n          source: {\n            uri: 'http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4',\n          },\n          isMuted: isMute,\n        }}\n        mute={{\n          enterMute: () => setIsMute(!isMute),\n          exitMute: () => setIsMute(!isMute),\n          isMute,\n        }}\n        style={{ height: 160 }}\n      />\n\n      <Text style={styles.text}>Fullscren icon hidden</Text>\n      <VideoPlayer\n        videoProps={{\n          shouldPlay: false,\n          resizeMode: ResizeMode.CONTAIN,\n          source: {\n            uri: 'http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4',\n          },\n        }}\n        fullscreen={{\n          visible: false,\n        }}\n        style={{ height: 160 }}\n      />\n\n      <Text style={styles.text}>Ref - clicking on Enter/Exit fullscreen changes playing</Text>\n      <VideoPlayer\n        videoProps={{\n          shouldPlay: false,\n          resizeMode: ResizeMode.CONTAIN,\n          source: {\n            uri: 'http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4',\n          },\n          ref: refVideo,\n        }}\n        fullscreen={{\n          enterFullscreen: () => {\n            setInFullsreen(!inFullscreen)\n            refVideo.current.setStatusAsync({\n              shouldPlay: true,\n            })\n          },\n          exitFullscreen: () => {\n            setInFullsreen(!inFullscreen)\n            refVideo.current.setStatusAsync({\n              shouldPlay: false,\n            })\n          },\n          inFullscreen,\n        }}\n        style={{ height: 160 }}\n      />\n\n      <Text style={styles.text}>Fullscren</Text>\n      <VideoPlayer\n        videoProps={{\n          shouldPlay: false,\n          resizeMode: ResizeMode.CONTAIN,\n          source: {\n            uri: 'http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4',\n          },\n          ref: refVideo2,\n        }}\n        fullscreen={{\n          inFullscreen: inFullscreen2,\n          enterFullscreen: async () => {\n            setStatusBarHidden(true, 'fade')\n            setInFullsreen2(!inFullscreen2)\n            await ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.LANDSCAPE_LEFT)\n            refVideo2.current.setStatusAsync({\n              shouldPlay: true,\n            })\n          },\n          exitFullscreen: async () => {\n            setStatusBarHidden(false, 'fade')\n            setInFullsreen2(!inFullscreen2)\n            await ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.DEFAULT)\n          },\n        }}\n        style={{\n          videoBackgroundColor: 'black',\n          height: inFullscreen2 ? Dimensions.get('window').width : 160,\n          width: inFullscreen2 ? Dimensions.get('window').height : 320,\n        }}\n      />\n\n      <Text style={styles.text}>Custom title</Text>\n      <VideoPlayer\n        videoProps={{\n          shouldPlay: false,\n          resizeMode: ResizeMode.CONTAIN,\n          source: {\n            uri: 'http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4',\n          },\n        }}\n        style={{\n          videoBackgroundColor: 'black',\n        }}\n        header={<Text style={{ color: '#FFF' }}>Custom title</Text>}\n      />\n    </ScrollView>\n  )\n}\n\nconst styles = StyleSheet.create({\n  container: {\n    backgroundColor: '#FFF',\n    flex: 1,\n  },\n  contentContainer: {\n    alignItems: 'center',\n    justifyContent: 'center',\n    paddingTop: 40,\n  },\n  text: {\n    marginTop: 36,\n    marginBottom: 12,\n  },\n})\n\nexport default App\n"
  },
  {
    "path": "example-app/app.json",
    "content": "{\n  \"expo\": {\n    \"name\": \"example-app\",\n    \"slug\": \"example-app\",\n    \"privacy\": \"public\",\n    \"platforms\": [\n      \"ios\",\n      \"android\",\n      \"web\"\n    ],\n    \"version\": \"1.0.0\",\n    \"orientation\": \"portrait\",\n    \"icon\": \"./assets/icon.png\",\n    \"splash\": {\n      \"image\": \"./assets/splash.png\",\n      \"resizeMode\": \"contain\",\n      \"backgroundColor\": \"#ffffff\"\n    },\n    \"updates\": {\n      \"fallbackToCacheTimeout\": 0\n    },\n    \"assetBundlePatterns\": [\n      \"**/*\"\n    ],\n    \"ios\": {\n      \"supportsTablet\": true\n    }\n  }\n}\n"
  },
  {
    "path": "example-app/babel.config.js",
    "content": "module.exports = function (api) {\n  api.cache(true)\n  return {\n    presets: ['babel-preset-expo'],\n  }\n}\n"
  },
  {
    "path": "example-app/package.json",
    "content": "{\n    \"private\": true,\n    \"main\": \"^node_modules/expo/AppEntry.js\",\n    \"scripts\": {\n        \"start\": \"^expo start\",\n        \"android\": \"^expo start --android\",\n        \"ios\": \"^expo start --ios\",\n        \"web\": \"^expo start --web\",\n        \"eject\": \"^expo eject\"\n    },\n    \"dependencies\": {\n        \"@react-native-community/slider\": \"4.2.3\",\n        \"expo\": \"^46.0.13\",\n        \"expo-av\": \"~12.0.4\",\n        \"expo-screen-orientation\": \"~4.3.0\",\n        \"expo-status-bar\": \"~1.4.0\",\n        \"expo-video-player\": \"^2.1.0\",\n        \"react\": \"18.0.0\",\n        \"react-dom\": \"18.0.0\",\n        \"react-native\": \"0.69.5\",\n        \"react-native-screens\": \"~3.15.0\",\n        \"react-native-web\": \"~0.18.7\"\n    },\n    \"devDependencies\": {\n        \"@babel/core\": \"^7.18.6\",\n        \"@types/react\": \"~18.0.0\",\n        \"@types/react-native\": \"~0.69.1\",\n        \"babel-preset-expo\": \"~9.2.0\",\n        \"typescript\": \"^4.6.3\"\n    }\n}\n"
  },
  {
    "path": "example-app/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"allowSyntheticDefaultImports\": true,\n    \"jsx\": \"react-native\",\n    \"lib\": [\n      \"dom\",\n      \"esnext\"\n    ],\n    \"moduleResolution\": \"node\",\n    \"noEmit\": true,\n    \"skipLibCheck\": true,\n    \"resolveJsonModule\": true\n  },\n  \"extends\": \"expo/tsconfig.base\"\n}\n"
  },
  {
    "path": "lib/constants.tsx",
    "content": "export enum ControlStates {\n  Visible = 'Visible',\n  Hidden = 'Hidden',\n}\n\nexport enum PlaybackStates {\n  Loading = 'Loading',\n  Playing = 'Playing',\n  Paused = 'Paused',\n  Buffering = 'Buffering',\n  Error = 'Error',\n  Ended = 'Ended',\n}\n\nexport enum ErrorSeverity {\n  Fatal = 'Fatal',\n  NonFatal = 'NonFatal',\n}\n\nexport type ErrorType = {\n  type: ErrorSeverity\n  message: string\n  obj: Record<string, unknown>\n}\n"
  },
  {
    "path": "lib/index.tsx",
    "content": "import { AVPlaybackStatus, Audio, Video } from 'expo-av'\nimport {\n  ActivityIndicator,\n  Animated,\n  StyleSheet,\n  Text,\n  TouchableWithoutFeedback,\n  View,\n} from 'react-native'\nimport { ControlStates, ErrorSeverity, PlaybackStates } from './constants'\nimport {\n  ErrorMessage,\n  TouchableButton,\n  deepMerge,\n  getMinutesSecondsFromMilliseconds,\n  styles,\n} from './utils'\nimport { MaterialIcons } from '@expo/vector-icons'\nimport { Props, defaultProps } from './props'\nimport { useEffect, useRef, useState } from 'react'\nimport React from 'react'\nimport Slider from '@react-native-community/slider'\n\nconst VideoPlayer = (tempProps: Props) => {\n  const props = deepMerge(defaultProps, tempProps) as Props\n\n  let playbackInstance: Video | null = null\n  let controlsTimer: NodeJS.Timeout | null = null\n  let initialShow = props.defaultControlsVisible\n  const header = props.header\n\n  const [errorMessage, setErrorMessage] = useState('')\n  const controlsOpacity = useRef(new Animated.Value(props.defaultControlsVisible ? 1 : 0)).current\n  const [controlsState, setControlsState] = useState(\n    props.defaultControlsVisible ? ControlStates.Visible : ControlStates.Hidden\n  )\n  const [playbackInstanceInfo, setPlaybackInstanceInfo] = useState({\n    position: 0,\n    duration: 0,\n    state: props.videoProps.source ? PlaybackStates.Loading : PlaybackStates.Error,\n  })\n\n  // We need to extract ref, because of misstypes in <Slider />\n  // eslint-disable-next-line @typescript-eslint/no-unused-vars\n  const { ref: sliderRef, ...sliderProps } = props.slider\n  const screenRatio = props.style.width! / props.style.height!\n\n  let videoHeight = props.style.height\n  let videoWidth = videoHeight! * screenRatio\n\n  if (videoWidth > props.style.width!) {\n    videoWidth = props.style.width!\n    videoHeight = videoWidth / screenRatio\n  }\n\n  useEffect(() => {\n    setAudio()\n\n    return () => {\n      if (playbackInstance) {\n        playbackInstance.setStatusAsync({\n          shouldPlay: false,\n        })\n      }\n    }\n  }, [])\n\n  useEffect(() => {\n    if (!props.videoProps.source) {\n      console.error(\n        '[VideoPlayer] `Source` is a required in `videoProps`. ' +\n          'Check https://docs.expo.io/versions/latest/sdk/video/#usage'\n      )\n      setErrorMessage('`Source` is a required in `videoProps`')\n      setPlaybackInstanceInfo({ ...playbackInstanceInfo, state: PlaybackStates.Error })\n    } else {\n      setPlaybackInstanceInfo({ ...playbackInstanceInfo, state: PlaybackStates.Playing })\n    }\n  }, [props.videoProps.source])\n\n  const hideAnimation = () => {\n    Animated.timing(controlsOpacity, {\n      toValue: 0,\n      duration: props.animation.fadeOutDuration,\n      useNativeDriver: true,\n    }).start(({ finished }) => {\n      if (finished) {\n        setControlsState(ControlStates.Hidden)\n      }\n    })\n  }\n\n  const animationToggle = () => {\n    if (controlsState === ControlStates.Hidden) {\n      Animated.timing(controlsOpacity, {\n        toValue: 1,\n        duration: props.animation.fadeInDuration,\n        useNativeDriver: true,\n      }).start(({ finished }) => {\n        if (finished) {\n          setControlsState(ControlStates.Visible)\n        }\n      })\n    } else if (controlsState === ControlStates.Visible) {\n      hideAnimation()\n    }\n\n    if (controlsTimer === null && props.autoHidePlayer) {\n      controlsTimer = setTimeout(() => {\n        if (\n          playbackInstanceInfo.state === PlaybackStates.Playing &&\n          controlsState === ControlStates.Hidden\n        ) {\n          hideAnimation()\n        }\n        if (controlsTimer) {\n          clearTimeout(controlsTimer)\n        }\n        controlsTimer = null\n      }, 2000)\n    }\n  }\n\n  // Set audio mode to play even in silent mode (like the YouTube app)\n  const setAudio = async () => {\n    try {\n      await Audio.setAudioModeAsync({\n        playsInSilentModeIOS: true,\n      })\n    } catch (e) {\n      props.errorCallback({\n        type: ErrorSeverity.NonFatal,\n        message: 'Audio.setAudioModeAsync',\n        obj: e as Record<string, unknown>,\n      })\n    }\n  }\n\n  const updatePlaybackCallback = (status: AVPlaybackStatus) => {\n    props.playbackCallback(status)\n\n    if (status.isLoaded) {\n      setPlaybackInstanceInfo({\n        ...playbackInstanceInfo,\n        position: status.positionMillis,\n        duration: status.durationMillis || 0,\n        state:\n          status.positionMillis === status.durationMillis\n            ? PlaybackStates.Ended\n            : status.isBuffering\n            ? PlaybackStates.Buffering\n            : status.shouldPlay\n            ? PlaybackStates.Playing\n            : PlaybackStates.Paused,\n      })\n      if (\n        (status.didJustFinish && controlsState === ControlStates.Hidden) ||\n        (status.isBuffering && controlsState === ControlStates.Hidden && initialShow)\n      ) {\n        animationToggle()\n        initialShow = false\n      }\n    } else {\n      if (status.isLoaded === false && status.error) {\n        const errorMsg = `Encountered a fatal error during playback: ${status.error}`\n        setErrorMessage(errorMsg)\n        props.errorCallback({ type: ErrorSeverity.Fatal, message: errorMsg, obj: {} })\n      }\n    }\n  }\n\n  const togglePlay = async () => {\n    if (controlsState === ControlStates.Hidden) {\n      return\n    }\n    const shouldPlay = playbackInstanceInfo.state !== PlaybackStates.Playing\n    if (playbackInstance !== null) {\n      await playbackInstance.setStatusAsync({\n        shouldPlay,\n        ...(playbackInstanceInfo.state === PlaybackStates.Ended && { positionMillis: 0 }),\n      })\n      setPlaybackInstanceInfo({\n        ...playbackInstanceInfo,\n        state:\n          playbackInstanceInfo.state === PlaybackStates.Playing\n            ? PlaybackStates.Paused\n            : PlaybackStates.Playing,\n      })\n      if (shouldPlay) {\n        animationToggle()\n      }\n    }\n  }\n\n  if (playbackInstanceInfo.state === PlaybackStates.Error) {\n    return (\n      <View\n        style={{\n          backgroundColor: props.style.videoBackgroundColor,\n          width: videoWidth,\n          height: videoHeight,\n        }}\n      >\n        <ErrorMessage style={props.textStyle} message={errorMessage} />\n      </View>\n    )\n  }\n\n  if (playbackInstanceInfo.state === PlaybackStates.Loading) {\n    return (\n      <View\n        style={{\n          backgroundColor: props.style.controlsBackgroundColor,\n          width: videoWidth,\n          height: videoHeight,\n          justifyContent: 'center',\n        }}\n      >\n        {props.icon.loading || <ActivityIndicator {...props.activityIndicator} />}\n      </View>\n    )\n  }\n\n  return (\n    <View\n      style={{\n        backgroundColor: props.style.videoBackgroundColor,\n        width: videoWidth,\n        height: videoHeight,\n        maxWidth: '100%',\n      }}\n    >\n      <Video\n        style={styles.videoWrapper}\n        {...props.videoProps}\n        ref={component => {\n          playbackInstance = component\n          if (props.videoProps.ref) {\n            props.videoProps.ref.current = component as Video\n          }\n        }}\n        onPlaybackStatusUpdate={updatePlaybackCallback}\n      />\n\n      <Animated.View\n        pointerEvents={controlsState === ControlStates.Visible ? 'auto' : 'none'}\n        style={[\n          styles.topInfoWrapper,\n          {\n            opacity: controlsOpacity,\n          },\n        ]}\n      >\n        {header}\n      </Animated.View>\n\n      <TouchableWithoutFeedback onPress={animationToggle}>\n        <Animated.View\n          style={{\n            ...StyleSheet.absoluteFillObject,\n            opacity: controlsOpacity,\n            justifyContent: 'center',\n            alignItems: 'center',\n          }}\n        >\n          <View\n            style={{\n              ...StyleSheet.absoluteFillObject,\n              backgroundColor: props.style.controlsBackgroundColor,\n              opacity: 0.5,\n            }}\n          />\n          <View pointerEvents={controlsState === ControlStates.Visible ? 'auto' : 'none'}>\n            <View style={styles.iconWrapper}>\n              <TouchableButton onPress={togglePlay}>\n                <View>\n                  {playbackInstanceInfo.state === PlaybackStates.Buffering &&\n                    (props.icon.loading || <ActivityIndicator {...props.activityIndicator} />)}\n                  {playbackInstanceInfo.state === PlaybackStates.Playing && props.icon.pause}\n                  {playbackInstanceInfo.state === PlaybackStates.Paused && props.icon.play}\n                  {playbackInstanceInfo.state === PlaybackStates.Ended && props.icon.replay}\n                  {((playbackInstanceInfo.state === PlaybackStates.Ended && !props.icon.replay) ||\n                    (playbackInstanceInfo.state === PlaybackStates.Playing && !props.icon.pause) ||\n                    (playbackInstanceInfo.state === PlaybackStates.Paused &&\n                      !props.icon.pause)) && (\n                    <MaterialIcons\n                      name={\n                        playbackInstanceInfo.state === PlaybackStates.Playing\n                          ? 'pause'\n                          : playbackInstanceInfo.state === PlaybackStates.Paused\n                          ? 'play-arrow'\n                          : 'replay'\n                      }\n                      style={props.icon.style}\n                      size={props.icon.size}\n                      color={props.icon.color}\n                    />\n                  )}\n                </View>\n              </TouchableButton>\n            </View>\n          </View>\n        </Animated.View>\n      </TouchableWithoutFeedback>\n\n      <Animated.View\n          pointerEvents={controlsState === ControlStates.Visible ? 'auto' : 'none'}\n          style={[\n          styles.bottomInfoWrapper,\n          {\n            opacity: controlsOpacity,\n          },\n        ]}\n      >\n        {props.timeVisible && (\n          <Text style={[props.textStyle, styles.timeLeft]}>\n            {getMinutesSecondsFromMilliseconds(playbackInstanceInfo.position)}\n          </Text>\n        )}\n        {props.slider.visible && (\n          <Slider\n            {...sliderProps}\n            style={[styles.slider, props.slider.style]}\n            value={\n              playbackInstanceInfo.duration\n                ? playbackInstanceInfo.position / playbackInstanceInfo.duration\n                : 0\n            }\n            onSlidingStart={() => {\n              if (playbackInstanceInfo.state === PlaybackStates.Playing) {\n                togglePlay()\n                setPlaybackInstanceInfo({ ...playbackInstanceInfo, state: PlaybackStates.Paused })\n              }\n            }}\n            onSlidingComplete={async e => {\n              const position = e * playbackInstanceInfo.duration\n              if (playbackInstance) {\n                await playbackInstance.setStatusAsync({\n                  positionMillis: position,\n                  shouldPlay: true,\n                })\n              }\n              setPlaybackInstanceInfo({\n                ...playbackInstanceInfo,\n                position,\n              })\n            }}\n          />\n        )}\n        {props.timeVisible && (\n          <Text style={[props.textStyle, styles.timeRight]}>\n            {getMinutesSecondsFromMilliseconds(playbackInstanceInfo.duration)}\n          </Text>\n        )}\n        {props.mute.visible && (\n          <TouchableButton\n            onPress={() => (props.mute.isMute ? props.mute.exitMute?.() : props.mute.enterMute?.())}\n          >\n            <View>\n              {props.icon.mute}\n              {props.icon.exitMute}\n              {((!props.icon.mute && props.mute.isMute) ||\n                (!props.icon.exitMute && !props.mute.isMute)) && (\n                <MaterialIcons\n                  name={props.mute.isMute ? 'volume-up' : 'volume-off'}\n                  style={props.icon.style}\n                  size={props.icon.size! / 2}\n                  color={props.icon.color}\n                />\n              )}\n            </View>\n          </TouchableButton>\n        )}\n        {props.fullscreen.visible && (\n          <TouchableButton\n            onPress={() =>\n              props.fullscreen.inFullscreen\n                ? props.fullscreen.exitFullscreen!()\n                : props.fullscreen.enterFullscreen!()\n            }\n          >\n            <View>\n              {!props.fullscreen.inFullscreen && props.icon.fullscreen}\n              {props.fullscreen.inFullscreen && props.icon.exitFullscreen}\n              {((!props.icon.fullscreen && !props.fullscreen.inFullscreen) ||\n                (!props.icon.exitFullscreen && props.fullscreen.inFullscreen)) && (\n                <MaterialIcons\n                  name={props.fullscreen.inFullscreen ? 'fullscreen-exit' : 'fullscreen'}\n                  style={props.icon.style}\n                  size={props.icon.size! / 2}\n                  color={props.icon.color}\n                />\n              )}\n            </View>\n          </TouchableButton>\n        )}\n      </Animated.View>\n    </View>\n  )\n}\n\nVideoPlayer.defaultProps = defaultProps\n\nexport default VideoPlayer\n"
  },
  {
    "path": "lib/props.tsx",
    "content": "import { AVPlaybackStatus, Video, VideoProps } from 'expo-av'\nimport { ActivityIndicatorProps, Dimensions, Platform, TextStyle } from 'react-native'\nimport { ColorValue } from 'react-native'\nimport { ErrorType } from './constants'\nimport { MutableRefObject, ReactNode } from 'react'\nimport { SliderProps } from '@react-native-community/slider'\n\n// https://github.com/typescript-cheatsheets/react/issues/415\nexport type Props = RequiredProps & DefaultProps\n\nexport const defaultProps = {\n  errorCallback: error =>\n    console.error(`[VideoPlayer] ${error.type} Error - ${error.message}: ${error.obj}`),\n  // eslint-disable-next-line @typescript-eslint/no-empty-function\n  playbackCallback: () => {},\n  defaultControlsVisible: false,\n  timeVisible: true,\n  slider: {\n    visible: true,\n  },\n  textStyle: {\n    color: '#FFF',\n    fontSize: 12,\n    textAlign: 'center',\n  },\n  activityIndicator: {\n    size: 'large',\n    color: '#999',\n  },\n  animation: {\n    fadeInDuration: 300,\n    fadeOutDuration: 300,\n  },\n  style: {\n    width: Platform.OS === 'web' ? '100%' : Dimensions.get('window').width,\n    height: Dimensions.get('window').height,\n    videoBackgroundColor: '#000',\n    controlsBackgroundColor: '#000',\n  },\n  icon: {\n    size: 48,\n    color: '#FFF',\n    style: {\n      padding: 2,\n    },\n  },\n  fullscreen: {\n    enterFullscreen: () =>\n      // eslint-disable-next-line no-console\n      console.log('[VideoPlayer] - missing `enterFullscreen` function in `fullscreen` prop'),\n    exitFullscreen: () =>\n      // eslint-disable-next-line no-console\n      console.log('[VideoPlayer] - missing `exitFullscreen` function in `fullscreen` prop'),\n    inFullscreen: false,\n    visible: true,\n  },\n  autoHidePlayer: true,\n  header: undefined,\n  mute: {\n    enterMute: () =>\n      // eslint-disable-next-line no-console\n      console.log('[VideoPlayer] - missing `enterMute` function in `mute` prop'),\n    exitMute: () =>\n      // eslint-disable-next-line no-console\n      console.log('[VideoPlayer] - missing `exitMute` function in `mute` prop'),\n    isMute: false,\n    visible: false,\n  },\n} as DefaultProps\n\ntype RequiredProps = {\n  videoProps: VideoProps & {\n    ref?: MutableRefObject<Video>\n  }\n}\n\ntype DefaultProps = {\n  errorCallback: (error: ErrorType) => void\n  playbackCallback: (status: AVPlaybackStatus) => void\n  defaultControlsVisible: boolean\n  timeVisible: boolean\n  textStyle: TextStyle\n  slider: {\n    visible?: boolean\n  } & SliderProps\n  activityIndicator: ActivityIndicatorProps\n  animation: {\n    fadeInDuration?: number\n    fadeOutDuration?: number\n  }\n  header: ReactNode\n  style: {\n    width?: number\n    height?: number\n    videoBackgroundColor?: ColorValue\n    controlsBackgroundColor?: ColorValue\n  }\n  icon: {\n    size?: number\n    color?: ColorValue\n    style?: TextStyle\n    pause?: JSX.Element\n    play?: JSX.Element\n    replay?: JSX.Element\n    loading?: JSX.Element\n    fullscreen?: JSX.Element\n    exitFullscreen?: JSX.Element\n    mute?: JSX.Element\n    exitMute?: JSX.Element\n  }\n  fullscreen: {\n    enterFullscreen?: () => void\n    exitFullscreen?: () => void\n    inFullscreen?: boolean\n    visible?: boolean\n  }\n  autoHidePlayer: boolean\n  mute: {\n    enterMute?: () => void\n    exitMute?: () => void\n    isMute?: boolean\n    visible?: boolean\n  }\n}\n"
  },
  {
    "path": "lib/utils.tsx",
    "content": "import {\n  Platform,\n  StyleSheet,\n  Text,\n  TextStyle,\n  TouchableNativeFeedback,\n  TouchableNativeFeedbackProps,\n  TouchableOpacity,\n  TouchableOpacityProps,\n  View,\n} from 'react-native'\nimport React from 'react'\n\nexport const ErrorMessage = ({ message, style }: { message: string; style: TextStyle }) => (\n  <View style={styles.errorWrapper}>\n    <Text style={style}>{message}</Text>\n  </View>\n)\n\nexport const getMinutesSecondsFromMilliseconds = (ms: number) => {\n  const totalSeconds = ms / 1000\n  const seconds = String(Math.floor(totalSeconds % 60))\n  const minutes = String(Math.floor(totalSeconds / 60))\n\n  return minutes.padStart(1, '0') + ':' + seconds.padStart(2, '0')\n}\n\ntype ButtonProps = (TouchableNativeFeedbackProps | TouchableOpacityProps) & {\n  children: React.ReactNode\n}\nexport const TouchableButton = (props: ButtonProps) =>\n  Platform.OS === 'android' ? (\n    <TouchableNativeFeedback\n      background={TouchableNativeFeedback.Ripple('white', true)}\n      {...props}\n    />\n  ) : (\n    <TouchableOpacity {...props} />\n  )\n\n// https://gist.github.com/ahtcx/0cd94e62691f539160b32ecda18af3d6#gistcomment-3585151\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport const deepMerge = (target: { [x: string]: any }, source: { [x: string]: any }) => {\n  const result = { ...target, ...source }\n  const keys = Object.keys(result)\n\n  for (const key of keys) {\n    const tprop = target[key]\n    const sprop = source[key]\n    if (typeof tprop === 'object' && typeof sprop === 'object') {\n      result[key] = deepMerge(tprop, sprop)\n    }\n  }\n\n  return result\n}\nexport const styles = StyleSheet.create({\n  errorWrapper: {\n    ...StyleSheet.absoluteFillObject,\n    paddingHorizontal: 20,\n    justifyContent: 'center',\n  },\n  videoWrapper: {\n    flex: 1,\n    justifyContent: 'center',\n  },\n  iconWrapper: {\n    borderRadius: 100,\n    overflow: 'hidden',\n    padding: 10,\n  },\n  bottomInfoWrapper: {\n    position: 'absolute',\n    flexDirection: 'row',\n    alignItems: 'center',\n    justifyContent: 'space-between',\n    flex: 1,\n    bottom: 0,\n    left: 0,\n    right: 0,\n  },\n  topInfoWrapper: {\n    position: 'absolute',\n    flexDirection: 'row',\n    alignItems: 'center',\n    justifyContent: 'space-between',\n    flex: 1,\n    top: 0,\n    left: 0,\n    right: 0,\n    zIndex: 999,\n  },\n  timeLeft: { backgroundColor: 'transparent', marginLeft: 5 },\n  timeRight: { backgroundColor: 'transparent', marginRight: 5 },\n  slider: { flex: 1, paddingHorizontal: 10 },\n})\n"
  },
  {
    "path": "migration-1x-to-2x.md",
    "content": "# Updating to version 2.x\n\nSome properties has been renamed, removed, but a lot of functionality has been added. Please check [README.md](https://github.com/ihmpavel/expo-video-player/blob/master/README.md)\n\nOld property name | State | Current property name | Description\n---- | :--: | :-----: | -----------\n**debug** | ❌ | - | This prop has been removed\n**videoProps** | ✔️ | **videoProps** | Not changed\n**width** | ⚠️ | **style.width** | Property moved to the object `style`\n**height** | ⚠️ | **style.height** | Property moved to the object `style`\n**videoBackground** | ⚠️ | **style.videoBackground** | Property moved to the object `style`\n**videoRef** | ⚠️ | **videoProps.ref** | Property moved to the object `videoProps`. See usage in the [example-app](https://github.com/ihmpavel/expo-video-player/blob/master/example-app/App.tsx)\n**fadeInDuration** | ⚠️ | **animation.fadeInDuration** | Property moved to the object `animation`\n**fadeOutDuration** | ⚠️ | **animation.fadeOutDuration** | Property moved to the object `animation`\n**hideControlsTimerDuration** | ❌ | - | Prop has been removed\n**quickFadeOutDuration** | ❌ | - | Prop has been removed\n**errorCallback** | ✔️ | - | Not changed\n**playbackCallback** | ✔️ | - | Not changed\n**textStyle** | ✔️ | - | Not changed\n**inFullscreen** | ⚠️ | **fullscreen.inFullscreen** | Property moved to the object `fullscreen`\n**showFullscreenButton** | ⚠️ | **fullscreen.visible** | Property moved to the object `fullscreen`\n**switchToLandscape** | ⚠️ | **fullscreen.enterFullscreen** | Property moved to the object `fullscreen`\n**switchToPortrait** | ⚠️ | **fullscreen.exitFullscreen** | Property moved to the object `fullscreen`\n**thumbImage** | ⚠️ | **slider.thumbImage** | Property moved to the object `slider`. You can use any of the props (except `ref`, `value`, `onSlidingStart` and `onSlidingComplete`) from [@react-native-community/slider](https://github.com/callstack/react-native-slider)\n**iosTrackImage** | ⚠️ | **slider.trackImage** | Property moved to the object `slider`. You can use any of the props (except `ref`, `value`, `onSlidingStart` and `onSlidingComplete`) from [@react-native-community/slider](https://github.com/callstack/react-native-slider)\n**sliderColor** | ⚠️ | **slider.minimumTrackTintColor** | Property moved to the object `slider`. You can use any of the props (except `ref`, `value`, `onSlidingStart` and `onSlidingComplete`) from [@react-native-community/slider](https://github.com/callstack/react-native-slider)\n**disableSlider** | ⚠️ | **slider.visible** | Property moved to the object `slider`. You can use any of the props (except `ref`, `value`, `onSlidingStart` and `onSlidingComplete`) from [@react-native-community/slider](https://github.com/callstack/react-native-slider)\n**showControlsOnLoad** | ⚠️ | **defaultControlsVisible** | Prop has been renamed\n**fullscreenEnterIcon** | ⚠️ | **icon.fullscreenEnter** | Property moved to the object `icon`\n**fullscreenExitIcon** | ⚠️ | **icon.fullscreenExit** | Property moved to the object `icon`\n**playIcon** | ⚠️ | **icon.play** | Property moved to the object `icon`\n**pauseIcon** | ⚠️ | **icon.pause** | Property moved to the object `icon`\n**replayIcon** | ⚠️ | **icon.replay** | Property moved to the object `icon`\n**spinner** | ⚠️ | **icon.loading** | Property moved to the object `icon`\n\n## Guide\n- ❌ - Property removed\n- ⚠️ - Something changed\n- ✔️ - Nothing changed\n"
  },
  {
    "path": "package.json",
    "content": "{\n    \"name\": \"expo-video-player\",\n    \"version\": \"2.2.0\",\n    \"private\": false,\n    \"description\": \"Customizable Video Player controls for Expo\",\n    \"keywords\": [\n        \"customizable\",\n        \"expo\",\n        \"player\",\n        \"react-native\",\n        \"video-player\",\n        \"expo-video-player\",\n        \"videoplayer\",\n        \"expo-videoplayer\"\n    ],\n    \"homepage\": \"https://github.com/ihmpavel/expo-video-player\",\n    \"bugs\": \"https://github.com/ihmpavel/expo-video-player/issues\",\n    \"repository\": {\n        \"type\": \"git\"\n    },\n    \"license\": \"MIT\",\n    \"author\": \"Pavel Ihm\",\n    \"main\": \"dist/index.js\",\n    \"types\": \"dist/index.d.ts\",\n    \"scripts\": {\n        \"build\": \"rm -rf dist && tsc\",\n        \"lint\": \"eslint \\\"lib/**/*.{js,ts,tsx}\\\"\",\n        \"lint:fix\": \"eslint \\\"lib/**/*.{js,ts,tsx}\\\" --fix\"\n    },\n    \"devDependencies\": {\n        \"@react-native-community/slider\": \"^4.3.1\",\n        \"@types/react\": \"^18.0.21\",\n        \"@types/react-native\": \"^0.70.4\",\n        \"@typescript-eslint/eslint-plugin\": \"^5.38.1\",\n        \"@typescript-eslint/parser\": \"^5.38.1\",\n        \"eslint\": \"^8.24.0\",\n        \"eslint-config-prettier\": \"^8.5.0\",\n        \"eslint-config-react-app\": \"^7.0.1\",\n        \"eslint-plugin-import\": \"^2.26.0\",\n        \"eslint-plugin-prettier\": \"^4.2.1\",\n        \"eslint-plugin-react\": \"^7.31.8\",\n        \"eslint-plugin-react-hooks\": \"^4.6.0\",\n        \"eslint-plugin-react-native\": \"^4.0.0\",\n        \"eslint-plugin-sort-imports-es6-autofix\": \"^0.6.0\",\n        \"expo\": \"^46.0.13\",\n        \"expo-av\": \"^12.0.4\",\n        \"prettier\": \"^2.7.1\",\n        \"react\": \"^18.2.0\",\n        \"react-dom\": \"^18.2.0\",\n        \"react-native\": \"^0.70.1\",\n        \"typescript\": \"^4.8.4\"\n    },\n    \"peerDependencies\": {\n        \"@react-native-community/slider\": \">=4.0.0\",\n        \"expo\": \">=38.0.0\",\n        \"expo-av\": \">=5.0.2\"\n    },\n    \"dependencies\": {\n        \"tslib\": \"^2.4.0\"\n    }\n}\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"es6\",\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"node\",\n    \"lib\": [\n      \"dom\",\n      \"dom.iterable\",\n      \"esnext\"\n    ],\n    \"allowJs\": true,\n    \"checkJs\": true,\n    \"jsx\": \"react-native\",\n    \"declaration\": true,\n    \"outDir\": \"./dist\",\n    \"importHelpers\": true,\n    \"downlevelIteration\": true,\n    \"strict\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noImplicitReturns\": true,\n    \"noFallthroughCasesInSwitch\": true,\n    \"noUncheckedIndexedAccess\": true,\n    \"noPropertyAccessFromIndexSignature\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"esModuleInterop\": true,\n    \"resolveJsonModule\": true,\n    \"skipLibCheck\": true,\n    \"forceConsistentCasingInFileNames\": true\n  },\n  \"include\": [\n    \"lib\"\n  ],\n  \"exclude\": [\n    \"node_modules\",\n    \"dist\",\n    \"example-app\",\n    \"lib/**/__tests__/\",\n    \"lib/setupTests.ts\"\n  ],\n}"
  }
]