diff --git a/.eslintrc b/.eslintrc
index 24a2055d..e67012ec 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -1,3 +1,10 @@
 {
-  "extends": "@react-native-community",
+  "extends": [
+    "@react-native",
+    "eslint:recommended",
+    "plugin:react/recommended"
+  ],
+  "parserOptions": {
+    "requireConfigFile": false
+  }
 }
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index ec7299e4..468c5880 100644
--- a/.gitignore
+++ b/.gitignore
@@ -54,4 +54,9 @@ android/keystores/debug.keystore
 # windows
 Deploy.binlog
 msbuild.binlog
-android/buildOutput_*
\ No newline at end of file
+android/buildOutput_*
+
+# lib build
+lib/
+!src/lib
+*.tsbuildinfo
\ No newline at end of file
diff --git a/.prettierrc b/.prettierrc
index 1d4c3eff..2a3043bd 100644
--- a/.prettierrc
+++ b/.prettierrc
@@ -1,8 +1,6 @@
 {
-  "requirePragma": true,
   "singleQuote": true,
   "trailingComma": "all",
   "bracketSpacing": false,
-  "jsxBracketSameLine": true,
-  "parser": "flow"
+  "jsxBracketSameLine": true
 }
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d87c2de0..f09b85ce 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,7 @@
 ## Changelog
 
 ## Next
+- All: add built-in typescript support [#3266](https://github.com/react-native-video/react-native-video/pull/3266)
 - iOS, Android: expose playback functions to ref [#3245](https://github.com/react-native-video/react-native-video/pull/3245)
 - Windows: fix build error from over-specified SDK version [#3246](https://github.com/react-native-video/react-native-video/pull/3246)
 - Windows: fix `onError` not being raised [#3247](https://github.com/react-native-video/react-native-video/pull/3247)
diff --git a/Video.js b/Video.js
deleted file mode 100644
index c6abf501..00000000
--- a/Video.js
+++ /dev/null
@@ -1,587 +0,0 @@
-import React, { Component } from 'react';
-import PropTypes from 'prop-types';
-import { StyleSheet, requireNativeComponent, NativeModules, UIManager, View, Image, Platform, findNodeHandle } from 'react-native';
-import { ViewPropTypes, ImagePropTypes } from 'deprecated-react-native-prop-types';
-import resolveAssetSource from 'react-native/Libraries/Image/resolveAssetSource';
-import TextTrackType from './TextTrackType';
-import FilterType from './FilterType';
-import DRMType from './DRMType';
-import VideoResizeMode from './VideoResizeMode.js';
-
-const styles = StyleSheet.create({
-  base: {
-    overflow: 'hidden',
-  },
-});
-
-const { VideoDecoderProperties } = NativeModules;
-export { TextTrackType, FilterType, DRMType, VideoDecoderProperties };
-
-export default class Video extends Component {
-
-  constructor(props) {
-    super(props);
-
-    this.state = {
-      showPoster: !!props.poster,
-    };
-  }
-
-  setNativeProps(nativeProps) {
-    this._root.setNativeProps(nativeProps);
-  }
-
-  toTypeString(x) {
-    switch (typeof x) {
-      case 'object':
-        return x instanceof Date
-          ? x.toISOString()
-          : JSON.stringify(x); // object, null
-      case 'undefined':
-        return '';
-      default: // boolean, number, string
-        return x.toString();
-    }
-  }
-
-  stringsOnlyObject(obj) {
-    const strObj = {};
-
-    Object.keys(obj).forEach(x => {
-      strObj[x] = this.toTypeString(obj[x]);
-    });
-
-    return strObj;
-  }
-
-  seek = (time, tolerance = 100) => {
-    if (isNaN(time)) {throw new Error('Specified time is not a number');}
-
-    if (Platform.OS === 'ios') {
-      this.setNativeProps({
-        seek: {
-          time,
-          tolerance,
-        },
-      });
-    } else {
-      this.setNativeProps({ seek: time });
-    }
-  };
-
-  presentFullscreenPlayer = () => {
-    this.setNativeProps({ fullscreen: true });
-  };
-
-  dismissFullscreenPlayer = () => {
-    this.setNativeProps({ fullscreen: false });
-  };
-
-  save = async (options) => {
-    return await NativeModules.VideoManager.save(options, findNodeHandle(this._root));
-  }
-
-  pause = async () => {
-    await this.setPlayerPauseState(true);
-  };
-
-  play = async () => {
-    await this.setPlayerPauseState(false);
-  };
-
-  setPlayerPauseState = async (paused) => {
-    return await NativeModules.VideoManager.setPlayerPauseState(paused, findNodeHandle(this._root));
-  };
-
-  restoreUserInterfaceForPictureInPictureStopCompleted = (restored) => {
-    this.setNativeProps({ restoreUserInterfaceForPIPStopCompletionHandler: restored });
-  };
-
-  _assignRoot = (component) => {
-    this._root = component;
-  };
-
-  _hidePoster = () => {
-    if (this.state.showPoster) {
-      this.setState({ showPoster: false });
-    }
-  }
-
-  _onLoadStart = (event) => {
-    if (this.props.onLoadStart) {
-      this.props.onLoadStart(event.nativeEvent);
-    }
-  };
-
-  _onPlaybackStateChanged = (event) => {
-    if (this.props.onPlaybackStateChanged) {
-      this.props.onPlaybackStateChanged(event.nativeEvent);
-    }
-  };
-
-  _onLoad = (event) => {
-    // Need to hide poster here for windows as onReadyForDisplay is not implemented
-    if (Platform.OS === 'windows') {
-      this._hidePoster();
-    }
-    if (this.props.onLoad) {
-      this.props.onLoad(event.nativeEvent);
-    }
-  };
-
-  _onAudioTracks = (event) => {
-    if (this.props.onAudioTracks) {
-      this.props.onAudioTracks(event.nativeEvent);
-    }
-  };
-
-  _onTextTracks = (event) => {
-    if (this.props.onTextTracks) {
-      this.props.onTextTracks(event.nativeEvent);
-    }
-  };
-
-  _onVideoTracks = (event) => {
-    if (this.props.onVideoTracks) {
-      this.props.onVideoTracks(event.nativeEvent);
-    }
-  };
-
-  _onError = (event) => {
-    if (this.props.onError) {
-      this.props.onError(event.nativeEvent);
-    }
-  };
-
-  _onProgress = (event) => {
-    if (this.props.onProgress) {
-      this.props.onProgress(event.nativeEvent);
-    }
-  };
-
-  _onBandwidthUpdate = (event) => {
-    if (this.props.onBandwidthUpdate) {
-      this.props.onBandwidthUpdate(event.nativeEvent);
-    }
-  };
-
-  _onSeek = (event) => {
-    if (this.props.onSeek) {
-      this.props.onSeek(event.nativeEvent);
-    }
-  };
-
-  _onEnd = (event) => {
-    if (this.props.onEnd) {
-      this.props.onEnd(event.nativeEvent);
-    }
-  };
-
-  _onTimedMetadata = (event) => {
-    if (this.props.onTimedMetadata) {
-      this.props.onTimedMetadata(event.nativeEvent);
-    }
-  };
-
-  _onFullscreenPlayerWillPresent = (event) => {
-    if (this.props.onFullscreenPlayerWillPresent) {
-      this.props.onFullscreenPlayerWillPresent(event.nativeEvent);
-    }
-  };
-
-  _onFullscreenPlayerDidPresent = (event) => {
-    if (this.props.onFullscreenPlayerDidPresent) {
-      this.props.onFullscreenPlayerDidPresent(event.nativeEvent);
-    }
-  };
-
-  _onFullscreenPlayerWillDismiss = (event) => {
-    if (this.props.onFullscreenPlayerWillDismiss) {
-      this.props.onFullscreenPlayerWillDismiss(event.nativeEvent);
-    }
-  };
-
-  _onFullscreenPlayerDidDismiss = (event) => {
-    if (this.props.onFullscreenPlayerDidDismiss) {
-      this.props.onFullscreenPlayerDidDismiss(event.nativeEvent);
-    }
-  };
-
-  _onReadyForDisplay = (event) => {
-    if (!this.props.audioOnly) {
-      this._hidePoster();
-    }
-
-    if (this.props.onReadyForDisplay) {
-      this.props.onReadyForDisplay(event.nativeEvent);
-    }
-  };
-
-  _onPlaybackStalled = (event) => {
-    if (this.props.onPlaybackStalled) {
-      this.props.onPlaybackStalled(event.nativeEvent);
-    }
-  };
-
-  _onPlaybackResume = (event) => {
-    if (this.props.onPlaybackResume) {
-      this.props.onPlaybackResume(event.nativeEvent);
-    }
-  };
-
-  _onPlaybackRateChange = (event) => {
-    if (this.props.onPlaybackRateChange) {
-      this.props.onPlaybackRateChange(event.nativeEvent);
-    }
-  };
-
-  _onExternalPlaybackChange = (event) => {
-    if (this.props.onExternalPlaybackChange) {
-      this.props.onExternalPlaybackChange(event.nativeEvent);
-    }
-  }
-
-  _onAudioBecomingNoisy = () => {
-    if (this.props.onAudioBecomingNoisy) {
-      this.props.onAudioBecomingNoisy();
-    }
-  };
-
-  _onPictureInPictureStatusChanged = (event) => {
-    if (this.props.onPictureInPictureStatusChanged) {
-      this.props.onPictureInPictureStatusChanged(event.nativeEvent);
-    }
-  };
-
-  _onRestoreUserInterfaceForPictureInPictureStop = (event) => {
-    if (this.props.onRestoreUserInterfaceForPictureInPictureStop) {
-      this.props.onRestoreUserInterfaceForPictureInPictureStop();
-    }
-  };
-
-  _onAudioFocusChanged = (event) => {
-    if (this.props.onAudioFocusChanged) {
-      this.props.onAudioFocusChanged(event.nativeEvent);
-    }
-  };
-
-  _onBuffer = (event) => {
-    if (this.props.onBuffer) {
-      this.props.onBuffer(event.nativeEvent);
-    }
-  };
-
-  _onGetLicense = (event) => {
-    if (this.props.drm && this.props.drm.getLicense instanceof Function) {
-      const data = event.nativeEvent;
-      if (data && data.spcBase64) {
-        const getLicenseOverride = this.props.drm.getLicense(data.spcBase64, data.contentId, data.licenseUrl);
-        const getLicensePromise = Promise.resolve(getLicenseOverride); // Handles both scenarios, getLicenseOverride being a promise and not.
-        getLicensePromise.then((result => {
-          if (result !== undefined) {
-            NativeModules.VideoManager.setLicenseResult(result,  data.licenseUrl, findNodeHandle(this._root));
-          } else {
-            NativeModules.VideoManager.setLicenseError && NativeModules.VideoManager.setLicenseError('Empty license result',  data.licenseUrl, findNodeHandle(this._root));
-          }
-        })).catch((error) => {
-          NativeModules.VideoManager.setLicenseError && NativeModules.VideoManager.setLicenseError(error, data.licenseUrl, findNodeHandle(this._root));
-        });
-      } else {
-        NativeModules.VideoManager.setLicenseError && NativeModules.VideoManager.setLicenseError('No spc received', data.licenseUrl, findNodeHandle(this._root));
-      }
-    }
-  }
-
-  _onReceiveAdEvent = (event) => {
-    if (this.props.onReceiveAdEvent) {
-      this.props.onReceiveAdEvent(event.nativeEvent);
-    }
-  };
-
-  getViewManagerConfig = viewManagerName => {
-    if (!UIManager.getViewManagerConfig) {
-      return UIManager[viewManagerName];
-    }
-    return UIManager.getViewManagerConfig(viewManagerName);
-  };
-
-  render() {
-    const resizeMode = this.props.resizeMode;
-    const source = resolveAssetSource(this.props.source) || {};
-    const shouldCache = !source.__packager_asset;
-
-    let uri = source.uri || '';
-    if (uri && uri.match(/^\//)) {
-      uri = `file://${uri}`;
-    }
-
-    if (!uri) {
-      console.log('Trying to load empty source.');
-    }
-
-    const isNetwork = !!(uri && uri.match(/^https?:/i));
-    const isAsset = !!(uri && uri.match(/^(assets-library|ph|ipod-library|file|content|ms-appx|ms-appdata):/i));
-
-    if ((uri || uri === '') && !isNetwork && !isAsset) {
-      if (this.props.onError) {
-        this.props.onError({error: {errorString: 'invalid url, player will stop', errorCode: 'INVALID_URL'}});
-      }
-    }
-
-    let nativeResizeMode;
-    const RCTVideoInstance = this.getViewManagerConfig('RCTVideo');
-
-    if (resizeMode === VideoResizeMode.stretch) {
-      nativeResizeMode = RCTVideoInstance.Constants.ScaleToFill;
-    } else if (resizeMode === VideoResizeMode.contain) {
-      nativeResizeMode = RCTVideoInstance.Constants.ScaleAspectFit;
-    } else if (resizeMode === VideoResizeMode.cover) {
-      nativeResizeMode = RCTVideoInstance.Constants.ScaleAspectFill;
-    } else {
-      nativeResizeMode = RCTVideoInstance.Constants.ScaleNone;
-    }
-
-    const nativeProps = Object.assign({}, this.props);
-    Object.assign(nativeProps, {
-      style: [styles.base, nativeProps.style],
-      resizeMode: nativeResizeMode,
-      src: {
-        uri,
-        isNetwork,
-        isAsset,
-        shouldCache,
-        type: source.type || '',
-        mainVer: source.mainVer || 0,
-        patchVer: source.patchVer || 0,
-        requestHeaders: source.headers ? this.stringsOnlyObject(source.headers) : {},
-        startTime: source.startTime || 0,
-        endTime: source.endTime,
-        // Custom Metadata
-        title: source.title,
-        subtitle: source.subtitle,
-        description: source.description,
-      },
-      onVideoLoadStart: this._onLoadStart,
-      onVideoPlaybackStateChanged: this._onPlaybackStateChanged,
-      onVideoLoad: this._onLoad,
-      onAudioTracks: this._onAudioTracks,
-      onTextTracks: this._onTextTracks,
-      onVideoTracks: this._onVideoTracks,
-      onVideoError: this._onError,
-      onVideoProgress: this._onProgress,
-      onVideoSeek: this._onSeek,
-      onVideoEnd: this._onEnd,
-      onVideoBuffer: this._onBuffer,
-      onVideoBandwidthUpdate: this._onBandwidthUpdate,
-      onTimedMetadata: this._onTimedMetadata,
-      onVideoAudioBecomingNoisy: this._onAudioBecomingNoisy,
-      onVideoExternalPlaybackChange: this._onExternalPlaybackChange,
-      onVideoFullscreenPlayerWillPresent: this._onFullscreenPlayerWillPresent,
-      onVideoFullscreenPlayerDidPresent: this._onFullscreenPlayerDidPresent,
-      onVideoFullscreenPlayerWillDismiss: this._onFullscreenPlayerWillDismiss,
-      onVideoFullscreenPlayerDidDismiss: this._onFullscreenPlayerDidDismiss,
-      onReadyForDisplay: this._onReadyForDisplay,
-      onPlaybackStalled: this._onPlaybackStalled,
-      onPlaybackResume: this._onPlaybackResume,
-      onPlaybackRateChange: this._onPlaybackRateChange,
-      onAudioFocusChanged: this._onAudioFocusChanged,
-      onAudioBecomingNoisy: this._onAudioBecomingNoisy,
-      onGetLicense: nativeProps.drm && nativeProps.drm.getLicense && this._onGetLicense,
-      onPictureInPictureStatusChanged: this._onPictureInPictureStatusChanged,
-      onRestoreUserInterfaceForPictureInPictureStop: this._onRestoreUserInterfaceForPictureInPictureStop,
-      onReceiveAdEvent: this._onReceiveAdEvent,
-    });
-
-    const posterStyle = {
-      ...StyleSheet.absoluteFillObject,
-      resizeMode: this.props.posterResizeMode || 'contain',
-    };
-
-    return (
-      
-        
-        {this.state.showPoster && (
-          
-        )}
-      
-    );
-  }
-}
-
-Video.propTypes = {
-  filter: PropTypes.oneOf([
-    FilterType.NONE,
-    FilterType.INVERT,
-    FilterType.MONOCHROME,
-    FilterType.POSTERIZE,
-    FilterType.FALSE,
-    FilterType.MAXIMUMCOMPONENT,
-    FilterType.MINIMUMCOMPONENT,
-    FilterType.CHROME,
-    FilterType.FADE,
-    FilterType.INSTANT,
-    FilterType.MONO,
-    FilterType.NOIR,
-    FilterType.PROCESS,
-    FilterType.TONAL,
-    FilterType.TRANSFER,
-    FilterType.SEPIA,
-  ]),
-  filterEnabled: PropTypes.bool,
-  onVideoLoadStart: PropTypes.func,
-  onVideoLoad: PropTypes.func,
-  onVideoBuffer: PropTypes.func,
-  onVideoError: PropTypes.func,
-  onVideoProgress: PropTypes.func,
-  onVideoBandwidthUpdate: PropTypes.func,
-  onVideoSeek: PropTypes.func,
-  onVideoEnd: PropTypes.func,
-  onTimedMetadata: PropTypes.func,
-  onVideoAudioBecomingNoisy: PropTypes.func,
-  onVideoExternalPlaybackChange: PropTypes.func,
-  onVideoFullscreenPlayerWillPresent: PropTypes.func,
-  onVideoFullscreenPlayerDidPresent: PropTypes.func,
-  onVideoFullscreenPlayerWillDismiss: PropTypes.func,
-  onVideoFullscreenPlayerDidDismiss: PropTypes.func,
-
-  /* Wrapper component */
-  source: PropTypes.oneOfType([
-    PropTypes.shape({
-      uri: PropTypes.string,
-    }),
-    // Opaque type returned by require('./video.mp4')
-    PropTypes.number,
-  ]),
-  drm: PropTypes.shape({
-    type: PropTypes.oneOf([
-      DRMType.CLEARKEY, DRMType.FAIRPLAY, DRMType.WIDEVINE, DRMType.PLAYREADY,
-    ]),
-    licenseServer: PropTypes.string,
-    headers: PropTypes.shape({}),
-    base64Certificate: PropTypes.bool,
-    certificateUrl: PropTypes.string,
-    getLicense: PropTypes.func,
-  }),
-  localSourceEncryptionKeyScheme: PropTypes.string,
-  minLoadRetryCount: PropTypes.number,
-  maxBitRate: PropTypes.number,
-  resizeMode: PropTypes.string,
-  poster: PropTypes.string,
-  posterResizeMode: ImagePropTypes.resizeMode,
-  repeat: PropTypes.bool,
-  automaticallyWaitsToMinimizeStalling: PropTypes.bool,
-  allowsExternalPlayback: PropTypes.bool,
-  selectedAudioTrack: PropTypes.shape({
-    type: PropTypes.string.isRequired,
-    value: PropTypes.oneOfType([
-      PropTypes.string,
-      PropTypes.number,
-    ]),
-  }),
-  selectedVideoTrack: PropTypes.shape({
-    type: PropTypes.string.isRequired,
-    value: PropTypes.oneOfType([
-      PropTypes.string,
-      PropTypes.number,
-    ]),
-  }),
-  selectedTextTrack: PropTypes.shape({
-    type: PropTypes.string.isRequired,
-    value: PropTypes.oneOfType([
-      PropTypes.string,
-      PropTypes.number,
-    ]),
-  }),
-  textTracks: PropTypes.arrayOf(
-    PropTypes.shape({
-      title: PropTypes.string,
-      uri: PropTypes.string.isRequired,
-      type: PropTypes.oneOf([
-        TextTrackType.SRT,
-        TextTrackType.TTML,
-        TextTrackType.VTT,
-      ]),
-      language: PropTypes.string.isRequired,
-    })
-  ),
-  chapters: PropTypes.arrayOf(
-    PropTypes.shape({
-      title: PropTypes.string,
-      startTime: PropTypes.number.isRequired,
-      endTime: PropTypes.number.isRequired,
-    })
-  ),
-  paused: PropTypes.bool,
-  muted: PropTypes.bool,
-  volume: PropTypes.number,
-  bufferConfig: PropTypes.shape({
-    minBufferMs: PropTypes.number,
-    maxBufferMs: PropTypes.number,
-    bufferForPlaybackMs: PropTypes.number,
-    bufferForPlaybackAfterRebufferMs: PropTypes.number,
-    maxHeapAllocationPercent: PropTypes.number,
-  }),
-  rate: PropTypes.number,
-  pictureInPicture: PropTypes.bool,
-  playInBackground: PropTypes.bool,
-  preferredForwardBufferDuration: PropTypes.number,
-  playWhenInactive: PropTypes.bool,
-  ignoreSilentSwitch: PropTypes.oneOf(['ignore', 'obey']),
-  reportBandwidth: PropTypes.bool,
-  contentStartTime: PropTypes.number,
-  disableFocus: PropTypes.bool,
-  focusable: PropTypes.bool,
-  disableBuffering: PropTypes.bool,
-  controls: PropTypes.bool,
-  audioOnly: PropTypes.bool,
-  audioOutput: PropTypes.oneOf(['earpiece', 'speaker']),
-  fullscreenAutorotate: PropTypes.bool,
-  fullscreenOrientation: PropTypes.oneOf(['all', 'landscape', 'portrait']),
-  progressUpdateInterval: PropTypes.number,
-  subtitleStyle: PropTypes.shape({
-    paddingTop: PropTypes.number,
-    paddingBottom: PropTypes.number,
-    paddingLeft: PropTypes.number,
-    paddingRight: PropTypes.number,
-    fontSize: PropTypes.number,
-  }),
-  useTextureView: PropTypes.bool,
-  useSecureView: PropTypes.bool,
-  hideShutterView: PropTypes.bool,
-  shutterColor: PropTypes.string,
-  onLoadStart: PropTypes.func,
-  onPlaybackStateChanged: PropTypes.func,
-  onLoad: PropTypes.func,
-  onAudioTracks: PropTypes.func,
-  onTextTracks: PropTypes.func,
-  onVideoTracks: PropTypes.func,
-  onBuffer: PropTypes.func,
-  onError: PropTypes.func,
-  onProgress: PropTypes.func,
-  onBandwidthUpdate: PropTypes.func,
-  onSeek: PropTypes.func,
-  onEnd: PropTypes.func,
-  onFullscreenPlayerWillPresent: PropTypes.func,
-  onFullscreenPlayerDidPresent: PropTypes.func,
-  onFullscreenPlayerWillDismiss: PropTypes.func,
-  onFullscreenPlayerDidDismiss: PropTypes.func,
-  onReadyForDisplay: PropTypes.func,
-  onPlaybackStalled: PropTypes.func,
-  onPlaybackResume: PropTypes.func,
-  onPlaybackRateChange: PropTypes.func,
-  onAudioFocusChanged: PropTypes.func,
-  onAudioBecomingNoisy: PropTypes.func,
-  onPictureInPictureStatusChanged: PropTypes.func,
-  onExternalPlaybackChange: PropTypes.func,
-  adTagUrl: PropTypes.string,
-  onReceiveAdEvent: PropTypes.func,
-
-  /* Required by react-native */
-  ...ViewPropTypes,
-};
-
-const RCTVideo = requireNativeComponent('RCTVideo');
diff --git a/VideoResizeMode.js b/VideoResizeMode.js
deleted file mode 100644
index 15a89773..00000000
--- a/VideoResizeMode.js
+++ /dev/null
@@ -1,7 +0,0 @@
-import keyMirror from 'keymirror';
-
-export default keyMirror({
-  contain: null,
-  cover: null,
-  stretch: null,
-});
diff --git a/package.json b/package.json
index 5f6cf469..85cf9569 100644
--- a/package.json
+++ b/package.json
@@ -2,8 +2,9 @@
     "name": "react-native-video",
     "version": "6.0.0-alpha.8",
     "description": "A  element for react-native",
-    "main": "Video.js",
-    "source": "Video.js",
+    "main": "lib/index",
+    "source": "src/index",
+    "react-native": "src/index",
     "license": "MIT",
     "author": "Community Contributors",
     "homepage": "https://github.com/react-native-video/react-native-video#readme",
@@ -11,24 +12,32 @@
         "type": "git",
         "url": "git@github.com:react-native-video/react-native-video.git"
     },
+    "resolutions": {
+        "@types/react": "~18.0.0"
+    },
     "devDependencies": {
-        "@react-native-community/eslint-config": "^0.0.5",
-        "eslint": "^6.5.1",
-        "react": "16.9.0",
-        "react-native": "0.61.5",
-        "react-native-windows": "^0.61.0-0"
-    },
-    "dependencies": {
-        "deprecated-react-native-prop-types": "^2.2.0",
-        "keymirror": "^0.1.1",
-        "prop-types": "^15.7.2"
+        "@react-native/eslint-config": "^0.72.2",
+        "@types/jest": "^28.1.2",
+        "@types/react": "~18.0.0",
+        "@types/react-native": "0.72.3",
+        "@typescript-eslint/eslint-plugin": "^6.7.4",
+        "eslint": "^8.19.0",
+        "eslint-plugin-jest": "^27.4.2",
+        "jest": "^29.7.0",
+        "prettier": "^2.4.1",
+        "react": "18.2.0",
+        "react-native": "0.72.5",
+        "react-native-windows": "^0.61.0-0",
+        "typescript": "5.1.6"
     },
+    "dependencies": {},
     "peerDependencies": {
         "react": "*",
         "react-native": "*"
     },
     "scripts": {
         "lint": "yarn eslint .",
+        "build": "yarn tsc",
         "xbasic": "yarn --cwd examples/basic",
         "test": "echo no test available"
     },
@@ -36,11 +45,13 @@
         "android",
         "ios",
         "windows",
-        "FilterType.js",
-        "DRMType.js",
-        "TextTrackType.js",
-        "VideoResizeMode.js",
+        "src",
         "react-native-video.podspec",
-        "docs"
+        "docs",
+        "!android/build",
+        "!android/buildOutput_*",
+        "!android/local.properties",
+        "!ios/build",
+        "!**/*.tsbuildinfo"
     ]
 }
diff --git a/src/Video.tsx b/src/Video.tsx
new file mode 100644
index 00000000..d7b3e43e
--- /dev/null
+++ b/src/Video.tsx
@@ -0,0 +1,449 @@
+import React, {
+  useState,
+  useCallback,
+  useMemo,
+  useRef,
+  forwardRef,
+  useImperativeHandle,
+  type ComponentRef,
+} from "react";
+import {
+  View,
+  StyleSheet,
+  Image,
+  Platform,
+} from "react-native";
+import NativeVideoComponent, { RCTVideoConstants } from "./VideoNativeComponent";
+import type { NativeVideoResizeMode, OnAudioFocusChangedData, OnAudioTracksData, OnPlaybackStateChangedData, OnTextTracksData, OnTimedMetadataData, OnVideoErrorData, OnVideoTracksData } from './VideoNativeComponent'
+
+import type { StyleProp, ImageStyle, NativeSyntheticEvent } from "react-native";
+import {
+  type VideoComponentType,
+  type OnLoadData,
+  type OnGetLicenseData,
+  type OnLoadStartData,
+  type OnProgressData,
+  type OnSeekData,
+  type OnPictureInPictureStatusChangedData,
+  type OnBandwidthUpdateData,
+  type OnBufferData,
+  type OnExternalPlaybackChangeData,
+  type OnReceiveAdEventData,
+  VideoManager,
+} from "./VideoNativeComponent";
+import type { ReactVideoProps } from "./types/video";
+import { getReactTag, resolveAssetSourceForVideo } from "./utils";
+
+export interface VideoRef {
+  seek: (time: number, tolerance?: number) => void;
+  resume: () => void;
+  pause: () => void;
+  presentFullscreenPlayer: () => void;
+  dismissFullscreenPlayer: () => void;
+  restoreUserInterfaceForPictureInPictureStopCompleted: (restore: boolean) => void;
+}
+
+const Video = forwardRef(
+  (
+    {
+      source,
+      style,
+      resizeMode,
+      posterResizeMode,
+      poster,
+      fullscreen,
+      drm,
+      textTracks,
+      selectedAudioTrack,
+      selectedTextTrack,
+      onLoadStart,
+      onLoad,
+      onError,
+      onProgress,
+      onSeek,
+      onEnd,
+      onBuffer,
+      onBandwidthUpdate,
+      onExternalPlaybackChange,
+      onFullscreenPlayerWillPresent,
+      onFullscreenPlayerDidPresent,
+      onFullscreenPlayerWillDismiss,
+      onFullscreenPlayerDidDismiss,
+      onReadyForDisplay,
+      onPlaybackRateChange,
+      onAudioBecomingNoisy,
+      onPictureInPictureStatusChanged,
+      onRestoreUserInterfaceForPictureInPictureStop,
+      onReceiveAdEvent,
+      onPlaybackStateChanged,
+      onAudioFocusChanged,
+      onIdle,
+      onTimedMetadata,
+      onAudioTracks,
+      onTextTracks,
+      onVideoTracks,
+      ...rest
+    },
+    ref
+  ) => {
+    const nativeRef = useRef>(null);
+    const [showPoster, setShowPoster] = useState(!!poster);
+    const [isFullscreen, setIsFullscreen] = useState(fullscreen);
+    const [
+      _restoreUserInterfaceForPIPStopCompletionHandler,
+      setRestoreUserInterfaceForPIPStopCompletionHandler,
+    ] = useState();
+
+    const posterStyle = useMemo>(
+      () => ({
+        ...StyleSheet.absoluteFillObject,
+        resizeMode:
+          posterResizeMode && posterResizeMode !== "none"
+            ? posterResizeMode
+            : "contain",
+      }),
+      [posterResizeMode]
+    );
+
+    const src = useMemo(() => {
+      if (!source) return undefined;
+
+      const resolvedSource = resolveAssetSourceForVideo(source);
+      let uri = resolvedSource.uri || "";
+      if (uri && uri.match(/^\//)) uri = `file://${uri}`;
+      if (!uri) console.warn("Trying to load empty source");
+      const isNetwork = !!(uri && uri.match(/^https?:/));
+      const isAsset = !!(
+        uri &&
+        uri.match(
+          /^(assets-library|ipod-library|file|content|ms-appx|ms-appdata):/
+        )
+      );
+
+      return {
+        uri,
+        isNetwork,
+        isAsset,
+        shouldCache: resolvedSource.shouldCache || false,
+        type: resolvedSource.type || "",
+        mainVer: resolvedSource.mainVer || 0,
+        patchVer: resolvedSource.patchVer || 0,
+        requestHeaders: resolvedSource?.headers || {},
+        startTime: resolvedSource.startTime || 0,
+        endTime: resolvedSource.endTime
+      };
+    }, [source]);
+
+    const _resizeMode: NativeVideoResizeMode = useMemo(() => {
+      switch (resizeMode) {
+        case "contain":
+          return RCTVideoConstants.ScaleAspectFit;
+        case "cover":
+          return RCTVideoConstants.ScaleAspectFill;
+        case "stretch":
+          return RCTVideoConstants.ScaleToFill;
+        default:
+          return RCTVideoConstants.ScaleNone;
+      }
+    }, [resizeMode]);
+
+    const _drm = useMemo(() => {
+      if (!drm) return;
+      return {
+        drmType: drm.type,
+        licenseServer: drm.licenseServer,
+        headers: drm.headers,
+        contentId: drm.contentId,
+        certificateUrl: drm.certificateUrl,
+        base64Certificate: drm.base64Certificate,
+        useExternalGetLicense: !!drm.getLicense,
+      };
+    }, [drm]);
+
+
+    const _selectedTextTrack = useMemo(() => {
+      if (!selectedTextTrack) return;
+      if (typeof selectedTextTrack?.value === 'number') return {
+        seletedTextType: selectedTextTrack?.type,
+        index: selectedTextTrack?.value,
+      }
+      return {
+        selectedTextType: selectedTextTrack?.type,
+        value: selectedTextTrack?.value,
+      }
+    }, [selectedTextTrack]);
+
+    const _selectedAudioTrack = useMemo(() => {
+      if (!selectedAudioTrack) return;
+      if (typeof selectedAudioTrack?.value === 'number') return {
+        selectedAudioType: selectedAudioTrack?.type,
+        index: selectedAudioTrack?.value,
+      }
+      return {
+        selectedAudioType: selectedAudioTrack?.type,
+        value: selectedAudioTrack?.value,
+      }
+    }, [selectedAudioTrack]);
+
+
+    const seek = useCallback(
+      async (time: number, tolerance?: number) => {
+        if (isNaN(time)) throw new Error("Specified time is not a number");
+
+        if (!nativeRef.current) {
+          console.warn("Video Component is not mounted");
+          return;
+        }
+        
+        Platform.select({
+          ios: () => {
+            nativeRef.current?.setNativeProps({
+              seek: {
+                time,
+                tolerance,
+              },
+            });
+          },
+          default: () => {
+            nativeRef.current?.setNativeProps({
+              seek: time,
+            });
+          },
+        })();
+      },
+      []
+    );
+
+    const presentFullscreenPlayer = useCallback(() => {
+      setIsFullscreen(true);
+    }, [setIsFullscreen]);
+
+    const dismissFullscreenPlayer = useCallback(() => {
+      setIsFullscreen(false);
+    }, [setIsFullscreen]);
+
+    const save = useCallback(async () => {
+      await VideoManager.save(getReactTag(nativeRef));
+    }, []);
+
+    const pause = useCallback(async () => {
+      await VideoManager.setPlayerPauseState(true, getReactTag(nativeRef));
+    }, []);
+
+    const resume = useCallback(async () => {
+      await VideoManager.setPlayerPauseState(false, getReactTag(nativeRef));
+    }, []);
+
+    const restoreUserInterfaceForPictureInPictureStopCompleted = useCallback(
+      (restored: boolean) => {
+        setRestoreUserInterfaceForPIPStopCompletionHandler(restored);
+      },
+      [setRestoreUserInterfaceForPIPStopCompletionHandler]
+    );
+
+    const onVideoLoadStart = useCallback(
+      (e: NativeSyntheticEvent) => {
+        onLoadStart?.(e.nativeEvent);
+      },
+      [onLoadStart]
+    );
+
+    const onVideoLoad = useCallback(
+      (e: NativeSyntheticEvent) => {
+        if (Platform.OS === "windows") setShowPoster(false);
+        onLoad?.(e.nativeEvent);
+      },
+      [onLoad, setShowPoster]
+    );
+
+    const onVideoError = useCallback(
+      (e: NativeSyntheticEvent) => {
+        onError?.(e.nativeEvent);
+      },
+      [onError]
+    );
+
+    const onVideoProgress = useCallback(
+      (e: NativeSyntheticEvent) => {
+        onProgress?.(e.nativeEvent);
+      },
+      [onProgress]
+    );
+
+    const onVideoSeek = useCallback(
+      (e: NativeSyntheticEvent) => {
+        onSeek?.(e.nativeEvent);
+      },
+      [onSeek]
+    );
+
+    // android only
+    const onVideoPlaybackStateChanged = useCallback((e: NativeSyntheticEvent) => {
+      onPlaybackStateChanged?.(e.nativeEvent);
+    }, [onPlaybackStateChanged])
+
+    // android only
+    const onVideoIdle = useCallback(() => {
+      onIdle?.()
+    }, [onIdle])
+
+    const _onTimedMetadata = useCallback(
+      (e: NativeSyntheticEvent) => {
+        onTimedMetadata?.(e.nativeEvent);
+      },
+      [onTimedMetadata]
+    );
+
+    const _onAudioTracks = useCallback((e: NativeSyntheticEvent) => {
+      onAudioTracks?.(e.nativeEvent)
+    }, [onAudioTracks])
+
+    const _onTextTracks = useCallback((e: NativeSyntheticEvent) => {
+      onTextTracks?.(e.nativeEvent)
+    }, [onTextTracks])
+
+    const _onVideoTracks = useCallback((e: NativeSyntheticEvent) => {
+      onVideoTracks?.(e.nativeEvent)
+    }, [onVideoTracks])
+
+    const _onPlaybackRateChange = useCallback(
+      (e: NativeSyntheticEvent>) => {
+        onPlaybackRateChange?.(e.nativeEvent);
+      },
+      [onPlaybackRateChange]
+    );
+
+    const _onReadyForDisplay = useCallback(() => {
+      setShowPoster(false);
+      onReadyForDisplay?.();
+    }, [setShowPoster, onReadyForDisplay]);
+
+    const _onPictureInPictureStatusChanged = useCallback(
+      (e: NativeSyntheticEvent) => {
+        onPictureInPictureStatusChanged?.(e.nativeEvent);
+      },
+      [onPictureInPictureStatusChanged]
+    );
+
+    const _onAudioFocusChanged = useCallback((e: NativeSyntheticEvent) => {
+      onAudioFocusChanged?.(e.nativeEvent)
+    }, [onAudioFocusChanged])
+
+    const onVideoBuffer = useCallback((e: NativeSyntheticEvent) => {
+      onBuffer?.(e.nativeEvent);
+    }, [onBuffer]);
+
+    const onVideoExternalPlaybackChange = useCallback((e: NativeSyntheticEvent) => {
+      onExternalPlaybackChange?.(e.nativeEvent);
+    }, [onExternalPlaybackChange])
+
+    const _onBandwidthUpdate = useCallback((e: NativeSyntheticEvent) => {
+      onBandwidthUpdate?.(e.nativeEvent);
+    }, [onBandwidthUpdate]);
+
+    const _onReceiveAdEvent = useCallback((e: NativeSyntheticEvent) => {
+      onReceiveAdEvent?.(e.nativeEvent);
+    }, [onReceiveAdEvent]);
+
+    const onGetLicense = useCallback(
+      (event: NativeSyntheticEvent) => {
+        if (drm && drm.getLicense instanceof Function) {
+          const data = event.nativeEvent;
+          if (data && data.spcBase64) {
+            const getLicenseOverride = drm.getLicense(data.spcBase64, data.contentId, data.licenseUrl);
+            const getLicensePromise = Promise.resolve(getLicenseOverride); // Handles both scenarios, getLicenseOverride being a promise and not.
+            getLicensePromise.then((result => {
+              if (result !== undefined) {
+                nativeRef.current && VideoManager.setLicenseResult(result, data.licenseUrl, getReactTag(nativeRef));
+              } else {
+                nativeRef.current && VideoManager.setLicenseResultError('Empty license result', data.licenseUrl, getReactTag(nativeRef));
+              }
+            })).catch(() => {
+              nativeRef.current && VideoManager.setLicenseResultError('fetch error', data.licenseUrl, getReactTag(nativeRef));
+            });
+          } else {
+            VideoManager.setLicenseResultError('No spc received', data.licenseUrl, getReactTag(nativeRef));
+          }
+        }
+    },
+      [drm]
+    );
+
+    useImperativeHandle(
+      ref,
+      () => ({
+        seek,
+        presentFullscreenPlayer,
+        dismissFullscreenPlayer,
+        save,
+        pause,
+        resume,
+        restoreUserInterfaceForPictureInPictureStopCompleted,
+      }),
+      [
+        seek,
+        presentFullscreenPlayer,
+        dismissFullscreenPlayer,
+        save,
+        pause,
+        resume,
+        restoreUserInterfaceForPictureInPictureStopCompleted,
+      ]
+    );
+
+    return (
+      
+        
+        {showPoster ? (
+          
+        ) : null}
+      
+    );
+  }
+);
+
+Video.displayName = "Video";
+export default Video;
\ No newline at end of file
diff --git a/src/VideoNativeComponent.ts b/src/VideoNativeComponent.ts
new file mode 100644
index 00000000..2cf6bbb6
--- /dev/null
+++ b/src/VideoNativeComponent.ts
@@ -0,0 +1,323 @@
+import type { HostComponent, NativeSyntheticEvent, ViewProps } from 'react-native';
+import { NativeModules, requireNativeComponent } from 'react-native';
+import { getViewManagerConfig } from './utils';
+
+// -------- There are types for native component (future codegen) --------
+// if you are looking for types for react component, see src/types/video.ts
+
+type Headers = Record
+
+type VideoSrc = Readonly<{
+  uri?: string;
+  isNetwork?: boolean;
+  isAsset?: boolean;
+  shouldCache?: boolean;
+  type?: string;
+  mainVer?: number;
+  patchVer?: number;
+  requestHeaders?: Headers;
+  startTime?: number;
+  endTime?: number;
+}>
+
+
+export type Filter = 'None' |
+'CIColorInvert' |
+'CIColorMonochrome' |
+'CIColorPosterize' |
+'CIFalseColor' |
+'CIMaximumComponent' |
+'CIMinimumComponent' |
+'CIPhotoEffectChrome' |
+'CIPhotoEffectFade' |
+'CIPhotoEffectInstant' |
+'CIPhotoEffectMono' |
+'CIPhotoEffectNoir' |
+'CIPhotoEffectProcess' |
+'CIPhotoEffectTonal' |
+'CIPhotoEffectTransfer' |
+'CISepiaTone';
+
+export type DrmType = 'widevine' | 'playready' | 'clearkey' | 'fairplay';
+
+type Drm = Readonly<{
+  drmType?: DrmType;
+  licenseServer?: string;
+  headers?: Headers;
+  contentId?: string; // ios
+  certificateUrl?: string; // ios
+  base64Certificate?: boolean; // ios default: false
+  useExternalGetLicense?: boolean; // ios
+}>
+
+type TextTracks = ReadonlyArray>
+
+type TextTrackType = 'system' | 'disabled' | 'title' | 'language' | 'index';
+
+type SelectedTextTrack = Readonly<{
+  selectedTextType: TextTrackType;
+  value?: string;
+  index?: number;
+}>
+
+type AudioTrackType = 'system' | 'disabled' | 'title' | 'language' | 'index';
+
+type SelectedAudioTrack = Readonly<{
+  selectedAudioType: AudioTrackType;
+  value?: string;
+  index?: number;
+}>
+
+export type Seek = Readonly<{
+  time: number;
+  tolerance?: number;
+}>
+
+type BufferConfig = Readonly<{
+  minBufferMs?: number;
+  maxBufferMs?: number;
+  bufferForPlaybackMs?: number;
+  bufferForPlaybackAfterRebufferMs?: number;
+  maxHeapAllocationPercent?: number;
+  minBackBufferMemoryReservePercent?: number;
+  minBufferMemoryReservePercent?: number;
+}>
+
+type SelectedVideoTrack = Readonly<{
+  type: 'auto' | 'disabled' | 'resolution' | 'index';
+  value?: number;
+}>
+
+type SubtitleStyle = Readonly<{
+  fontSize?: number;
+  paddingTop?: number;
+  paddingBottom?: number;
+  paddingLeft?: number;
+  paddingRight?: number;
+}>
+
+export type OnLoadData = Readonly<{
+  currentTime: number;
+  duration: number;
+  naturalSize: Readonly<{
+    width: number;
+    height: number;
+    orientation: 'portrait' | 'landscape';
+  }>;
+}>
+
+export type OnLoadStartData = Readonly<{
+  isNetwork: boolean;
+  type: string;
+  uri: string;
+}>
+
+export type OnBufferData = Readonly<{ isBuffering: boolean }>;
+
+
+export type OnProgressData = Readonly<{
+  currentTime: number;
+  playableDuration: number;
+  seekableDuration: number;
+}>
+
+export type OnBandwidthUpdateData = Readonly<{
+  bitrate: number;
+}>;
+
+export type OnSeekData = Readonly<{
+  currentTime: number;
+  seekTime: number;
+  finished: boolean;
+}>
+
+export type OnPlaybackStateChangedData = Readonly<{
+  isPlaying: boolean;
+}>
+
+export type OnTimedMetadataData = Readonly<{
+  metadata: ReadonlyArray>
+}>
+
+
+export type OnAudioTracksData = Readonly<{
+  audioTracks: ReadonlyArray>
+}>
+
+export type OnTextTracksData = Readonly<{
+  textTracks: ReadonlyArray>
+}>
+
+export type OnVideoTracksData = Readonly<{
+  videoTracks: ReadonlyArray>
+}>
+
+export type OnPlaybackData = Readonly<{
+  playbackRate: number;
+}>;
+
+export type OnExternalPlaybackChangeData = Readonly<{
+  isExternalPlaybackActive: boolean;
+}>
+
+export type OnGetLicenseData = Readonly<{
+  licenseUrl: string;
+  contentId: string;
+  spcBase64: string;
+}>
+
+export type OnPictureInPictureStatusChangedData = Readonly<{
+  isActive: boolean;
+}>
+
+export type OnReceiveAdEventData = Readonly<{
+  event: string;
+}>
+
+export type OnVideoErrorData = Readonly<{
+  error: string;
+}>
+
+export type OnAudioFocusChangedData = Readonly<{
+  hasFocus: boolean;
+}>
+
+export type NativeVideoResizeMode = 'ScaleNone' | 'ScaleToFill' | 'ScaleAspectFit' | 'ScaleAspectFill';
+export interface VideoNativeProps extends ViewProps {
+  src?: VideoSrc;
+  drm?: Drm;
+  adTagUrl?: string;
+  allowsExternalPlayback?: boolean; // ios, true
+  maxBitRate?: number;
+  resizeMode?: NativeVideoResizeMode;
+  repeat?: boolean;
+  automaticallyWaitsToMinimizeStalling?: boolean
+  textTracks?: TextTracks;
+  selectedTextTrack?: SelectedTextTrack;
+  selectedAudioTrack?: SelectedAudioTrack;
+  paused?: boolean;
+  muted?: boolean;
+  controls?: boolean;
+  filter?: Filter;
+  filterEnabled?: boolean;
+  volume?: number; // default 1.0
+  playInBackground?: boolean;
+  preventsDisplaySleepDuringVideoPlayback?: boolean;
+  preferredForwardBufferDuration?: number; //ios, 0
+  playWhenInactive?: boolean; // ios, false
+  pictureInPicture?: boolean; // ios, false
+  ignoreSilentSwitch?: 'inherit' | 'ignore' | 'obey'; // ios, 'inherit'
+  mixWithOthers?: 'inherit' | 'mix' | 'duck'; // ios, 'inherit'
+  rate?: number;
+  fullscreen?: boolean; // ios, false
+  fullscreenAutorotate?: boolean;
+  fullscreenOrientation?: 'all' | 'landscape' | 'portrait';
+  progressUpdateInterval?: number;
+  restoreUserInterfaceForPIPStopCompletionHandler?: boolean;
+  localSourceEncryptionKeyScheme?: string;
+
+  backBufferDurationMs?: number; // Android
+  bufferConfig?: BufferConfig; // Android
+  contentStartTime?: number; // Android
+  currentPlaybackTime?: number; // Android
+  disableDisconnectError?: boolean; // Android
+  focusable?: boolean; 	// Android
+  hideShutterView?: boolean; //	Android
+  minLoadRetryCount?: number;	// Android
+  reportBandwidth?: boolean; //Android
+  selectedVideoTrack?: SelectedVideoTrack; // android
+  subtitleStyle?: SubtitleStyle // android
+  trackId?: string; // Android
+  useTextureView?: boolean;	// Android
+  useSecureView?: boolean;	// Android
+
+  onVideoLoad?: (event: NativeSyntheticEvent) => void;
+  onVideoLoadStart?: (event: NativeSyntheticEvent) => void;
+  onVideoBuffer?: (event: NativeSyntheticEvent) => void;
+  onVideoError?: (event: NativeSyntheticEvent) => void;
+  onVideoProgress?: (event: NativeSyntheticEvent) => void;
+  onBandwidthUpdate?: (event: NativeSyntheticEvent) => void;
+  onVideoSeek?: (event: NativeSyntheticEvent) => void;
+  onVideoEnd?: (event: NativeSyntheticEvent>) => void; // all
+  onVideoAudioBecomingNoisy?: (event: NativeSyntheticEvent>) => void;
+  onVideoFullscreenPlayerWillPresent?: (event: NativeSyntheticEvent>) => void; // ios, android
+  onVideoFullscreenPlayerDidPresent?: (event: NativeSyntheticEvent>) => void;  // ios, android
+  onVideoFullscreenPlayerWillDismiss?: (event: NativeSyntheticEvent>) => void;  // ios, android
+  onVideoFullscreenPlayerDidDismiss?: (event: NativeSyntheticEvent>) => void;  // ios, android
+  onReadyForDisplay?: (event: NativeSyntheticEvent>) => void;
+  onPlaybackRateChange?: (event: NativeSyntheticEvent) => void; // all
+  onVideoExternalPlaybackChange?: (event: NativeSyntheticEvent) => void;
+  onGetLicense?: (event: NativeSyntheticEvent) => void;
+  onPictureInPictureStatusChanged?: (event: NativeSyntheticEvent) => void;
+  onRestoreUserInterfaceForPictureInPictureStop?: (event: NativeSyntheticEvent>) => void;
+  onReceiveAdEvent?: (event: NativeSyntheticEvent) => void;
+  onVideoPlaybackStateChanged?: (event: NativeSyntheticEvent) => void; // android only
+  onVideoIdle?: (event: NativeSyntheticEvent<{}>) => void; // android only (nowhere in document, so do not use as props. just type declaration)
+  onAudioFocusChanged?: (event: NativeSyntheticEvent) => void; // android only (nowhere in document, so do not use as props. just type declaration)
+  onTimedMetadata?: (event: NativeSyntheticEvent) => void; // ios, android
+  onAudioTracks?: (event: NativeSyntheticEvent) => void; // android
+  onTextTracks?: (event: NativeSyntheticEvent) => void; // android
+  onVideoTracks?: (event: NativeSyntheticEvent) => void; // android
+}
+
+export type VideoComponentType = HostComponent;
+
+export interface VideoManagerType {
+  save: (reactTag: number) => Promise;
+  setPlayerPauseState: (paused: boolean, reactTag: number) => Promise;
+  setLicenseResult: (result: string, licenseUrl: string, reactTag: number) => Promise;
+  setLicenseResultError: (error: string, licenseUrl: string, reactTag: number) => Promise;
+}
+
+export interface VideoDecoderPropertiesType {
+  getWidevineLevel: () => Promise;
+  isCodecSupported: (mimeType: string, width: number, height: number) => Promise<'unsupported' | 'hardware' | 'software'>;
+  isHEVCSupported: () => Promise<'unsupported' | 'hardware' | 'software'>;
+}
+
+export type VideoViewManagerConfig = {
+  Constants: {
+    ScaleNone: any;
+    ScaleToFill: any;
+    ScaleAspectFit: any;
+    ScaleAspectFill: any;
+  };
+  Commands: { [key: string]: number; };
+};
+
+export const VideoManager = NativeModules.VideoManager as VideoManagerType;
+export const VideoDecoderProperties = NativeModules.VideoDecoderProperties as VideoDecoderPropertiesType;
+export const RCTVideoConstants = (getViewManagerConfig('RCTVideo') as VideoViewManagerConfig).Constants;
+
+export default requireNativeComponent('RCTVideo') as VideoComponentType;
diff --git a/src/index.ts b/src/index.ts
new file mode 100644
index 00000000..7412ec80
--- /dev/null
+++ b/src/index.ts
@@ -0,0 +1,11 @@
+import Video from "./Video";
+
+export { default as FilterType } from './lib/FilterType';
+export { default as VideoResizeMode } from './lib/VideoResizeMode';
+export { default as TextTrackType } from './lib/TextTrackType';
+export { default as DRMType } from './lib/DRMType';
+export { VideoDecoderProperties } from './VideoNativeComponent';
+
+export type { VideoRef } from './Video';
+
+export default Video;
\ No newline at end of file
diff --git a/DRMType.js b/src/lib/DRMType.ts
similarity index 91%
rename from DRMType.js
rename to src/lib/DRMType.ts
index d873bd69..e59791fe 100644
--- a/DRMType.js
+++ b/src/lib/DRMType.ts
@@ -3,4 +3,4 @@ export default {
     PLAYREADY: 'playready',
     CLEARKEY: 'clearkey',
     FAIRPLAY: 'fairplay',
-};
+} as const;
diff --git a/FilterType.js b/src/lib/FilterType.ts
similarity index 97%
rename from FilterType.js
rename to src/lib/FilterType.ts
index 3d8d9cb4..e096415f 100644
--- a/FilterType.js
+++ b/src/lib/FilterType.ts
@@ -15,4 +15,4 @@ export default {
     TONAL: 'CIPhotoEffectTonal',
     TRANSFER: 'CIPhotoEffectTransfer',
     SEPIA: 'CISepiaTone',
-};
+} as const;
diff --git a/TextTrackType.js b/src/lib/TextTrackType.ts
similarity index 89%
rename from TextTrackType.js
rename to src/lib/TextTrackType.ts
index 9f06e5dd..c7a0dabd 100644
--- a/TextTrackType.js
+++ b/src/lib/TextTrackType.ts
@@ -2,4 +2,4 @@ export default {
   SRT: 'application/x-subrip',
   TTML: 'application/ttml+xml',
   VTT: 'text/vtt',
-};
+} as const;
diff --git a/src/lib/VideoResizeMode.ts b/src/lib/VideoResizeMode.ts
new file mode 100644
index 00000000..235803e2
--- /dev/null
+++ b/src/lib/VideoResizeMode.ts
@@ -0,0 +1,5 @@
+export default {
+  contain: 'contain',
+  cover: 'cover',
+  stretch: 'stretch',
+} as const;
\ No newline at end of file
diff --git a/src/types/events.ts b/src/types/events.ts
new file mode 100644
index 00000000..cbdacc9e
--- /dev/null
+++ b/src/types/events.ts
@@ -0,0 +1,30 @@
+import type { OnBandwidthUpdateData, OnBufferData, OnLoadData, OnLoadStartData, OnProgressData, OnSeekData, OnPlaybackData, OnExternalPlaybackChangeData, OnPictureInPictureStatusChangedData, OnReceiveAdEventData, OnVideoErrorData, OnPlaybackStateChangedData, OnAudioFocusChangedData, OnTimedMetadataData, OnAudioTracksData, OnTextTracksData, OnVideoTracksData } from "../VideoNativeComponent";
+
+export interface ReactVideoEvents {
+  onAudioBecomingNoisy?: () => void //Android, iOS
+  onAudioFocusChanged?: (e: OnAudioFocusChangedData) => void // Android
+  onIdle?: () => void // Android
+  onBandwidthUpdate?: (e: OnBandwidthUpdateData) => void //Android
+  onBuffer?: (e: OnBufferData) => void //Android, iOS
+  onEnd?: () => void //All
+  onError?: (e: OnVideoErrorData) => void //Android, iOS
+  onExternalPlaybackChange?: (e: OnExternalPlaybackChangeData) => void //iOS
+  onFullscreenPlayerWillPresent?: () => void //Android, iOS
+  onFullscreenPlayerDidPresent?: () => void //Android, iOS
+  onFullscreenPlayerWillDismiss?: () => void //Android, iOS
+  onFullscreenPlayerDidDismiss?: () => void //Android, iOS
+  onLoad?: (e: OnLoadData) => void //All
+  onLoadStart?: (e: OnLoadStartData) => void //All
+  onPictureInPictureStatusChanged?: (e: OnPictureInPictureStatusChangedData) => void //iOS
+  onPlaybackRateChange?: (e: OnPlaybackData) => void //All
+  onProgress?: (e: OnProgressData) => void //All
+  onReadyForDisplay?: () => void //Android, iOS, Web
+  onReceiveAdEvent?: (e: OnReceiveAdEventData) => void //Android, iOS
+  onRestoreUserInterfaceForPictureInPictureStop?: () => void //iOS
+  onSeek?: (e: OnSeekData) => void //Android, iOS, Windows UWP
+  onPlaybackStateChanged?: (e: OnPlaybackStateChangedData) => void // Android
+  onTimedMetadata?: (e: OnTimedMetadataData) => void //Android, iOS
+  onAudioTracks?: (e: OnAudioTracksData) => void // Android
+  onTextTracks?: (e: OnTextTracksData) => void //Android
+  onVideoTracks?: (e: OnVideoTracksData) => void //Android
+}
\ No newline at end of file
diff --git a/src/types/language.ts b/src/types/language.ts
new file mode 100644
index 00000000..ec721833
--- /dev/null
+++ b/src/types/language.ts
@@ -0,0 +1,184 @@
+export type ISO639_1 =
+  | 'aa'
+  | 'ab'
+  | 'ae'
+  | 'af'
+  | 'ak'
+  | 'am'
+  | 'an'
+  | 'ar'
+  | 'as'
+  | 'av'
+  | 'ay'
+  | 'az'
+  | 'ba'
+  | 'be'
+  | 'bg'
+  | 'bi'
+  | 'bm'
+  | 'bn'
+  | 'bo'
+  | 'br'
+  | 'bs'
+  | 'ca'
+  | 'ce'
+  | 'ch'
+  | 'co'
+  | 'cr'
+  | 'cs'
+  | 'cu'
+  | 'cv'
+  | 'cy'
+  | 'da'
+  | 'de'
+  | 'dv'
+  | 'dz'
+  | 'ee'
+  | 'el'
+  | 'en'
+  | 'eo'
+  | 'es'
+  | 'et'
+  | 'eu'
+  | 'fa'
+  | 'ff'
+  | 'fi'
+  | 'fj'
+  | 'fo'
+  | 'fr'
+  | 'fy'
+  | 'ga'
+  | 'gd'
+  | 'gl'
+  | 'gn'
+  | 'gu'
+  | 'gv'
+  | 'ha'
+  | 'he'
+  | 'hi'
+  | 'ho'
+  | 'hr'
+  | 'ht'
+  | 'hu'
+  | 'hy'
+  | 'hz'
+  | 'ia'
+  | 'id'
+  | 'ie'
+  | 'ig'
+  | 'ii'
+  | 'ik'
+  | 'io'
+  | 'is'
+  | 'it'
+  | 'iu'
+  | 'ja'
+  | 'jv'
+  | 'ka'
+  | 'kg'
+  | 'ki'
+  | 'kj'
+  | 'kk'
+  | 'kl'
+  | 'km'
+  | 'kn'
+  | 'ko'
+  | 'kr'
+  | 'ks'
+  | 'ku'
+  | 'kv'
+  | 'kw'
+  | 'ky'
+  | 'la'
+  | 'lb'
+  | 'lg'
+  | 'li'
+  | 'ln'
+  | 'lo'
+  | 'lt'
+  | 'lu'
+  | 'lv'
+  | 'mg'
+  | 'mh'
+  | 'mi'
+  | 'mk'
+  | 'ml'
+  | 'mn'
+  | 'mr'
+  | 'ms'
+  | 'mt'
+  | 'my'
+  | 'na'
+  | 'nb'
+  | 'nd'
+  | 'ne'
+  | 'ng'
+  | 'nl'
+  | 'nn'
+  | 'no'
+  | 'nr'
+  | 'nv'
+  | 'ny'
+  | 'oc'
+  | 'oj'
+  | 'om'
+  | 'or'
+  | 'os'
+  | 'pa'
+  | 'pi'
+  | 'pl'
+  | 'ps'
+  | 'pt'
+  | 'qu'
+  | 'rm'
+  | 'rn'
+  | 'ro'
+  | 'ru'
+  | 'rw'
+  | 'sa'
+  | 'sc'
+  | 'sd'
+  | 'se'
+  | 'sg'
+  | 'si'
+  | 'sk'
+  | 'sl'
+  | 'sm'
+  | 'sn'
+  | 'so'
+  | 'sq'
+  | 'sr'
+  | 'ss'
+  | 'st'
+  | 'su'
+  | 'sv'
+  | 'sw'
+  | 'ta'
+  | 'te'
+  | 'tg'
+  | 'th'
+  | 'ti'
+  | 'tk'
+  | 'tl'
+  | 'tn'
+  | 'to'
+  | 'tr'
+  | 'ts'
+  | 'tt'
+  | 'tw'
+  | 'ty'
+  | 'ug'
+  | 'uk'
+  | 'ur'
+  | 'uz'
+  | 've'
+  | 'vi'
+  | 'vo'
+  | 'wa'
+  | 'wo'
+  | 'xh'
+  | 'yi'
+  | 'yo'
+  | 'za'
+  | 'zh'
+  | 'zu';
\ No newline at end of file
diff --git a/src/types/video.ts b/src/types/video.ts
new file mode 100644
index 00000000..2a824cc3
--- /dev/null
+++ b/src/types/video.ts
@@ -0,0 +1,135 @@
+import type { ISO639_1 } from './language';
+import type { ReactVideoEvents } from './events';
+import type { StyleProp, ViewStyle } from 'react-native'
+
+type Filter = | 'None'
+              | 'CIColorInvert'
+              | 'CIColorMonochrome'
+              | 'CIColorPosterize'
+              | 'CIFalseColor'
+              | 'CIMaximumComponent'
+              | 'CIMinimumComponent'
+              | 'CIPhotoEffectChrome'
+              | 'CIPhotoEffectFade'
+              | 'CIPhotoEffectInstant'
+              | 'CIPhotoEffectMono'
+              | 'CIPhotoEffectNoir'
+              | 'CIPhotoEffectProcess'
+              | 'CIPhotoEffectTonal'
+              | 'CIPhotoEffectTransfer'
+              | 'CISepiaTone'
+
+
+
+type Headers = Record;
+
+export type ReactVideoSource = Readonly<{
+  uri?: string;
+  isNetwork?: boolean;
+  isAsset?: boolean;
+  shouldCache?: boolean;
+  type?: string;
+  mainVer?: number;
+  patchVer?: number;
+  headers?: Headers;
+  startTime?: number;
+  endTime?: number;
+}>;
+
+export type ReactVideoDrm = Readonly<{
+  type?: 'widevine' | 'playready' | 'clearkey' | 'fairplay';
+  licenseServer?: string;
+  headers?: Headers;
+  contentId?: string; // ios
+  certificateUrl?: string; // ios
+  base64Certificate?: boolean; // ios default: false
+  getLicense?: (licenseUrl: string, contentId: string, spcBase64: string) => void; // ios
+}>
+
+type BufferConfig = {
+  minBufferMs?: number;
+  maxBufferMs?: number;
+  bufferForPlaybackMs?: number;
+  bufferForPlaybackAfterRebufferMs?: number;
+  maxHeapAllocationPercent?: number;
+  minBackBufferMemoryReservePercent?: number;
+  minBufferMemoryReservePercent?: number;
+}
+
+type SelectedTrack = {
+  type: 'system' | 'disabled' | 'title' | 'language' | 'index';
+  value?: string | number;
+}
+
+type SelectedVideoTrack = {
+  type: 'auto' | 'disabled' | 'resolution' | 'index'
+  value?: number;
+}
+
+type SubtitleStyle = {
+  fontSize?: number;
+  paddingTop?: number;
+  paddingBottom?: number;
+  paddingLeft?: number;
+  paddingRight?: number;
+}
+
+type TextTracks = {
+  title: string;
+  language: ISO639_1;
+  type: | 'application/x-subrip'
+        | 'application/ttml+xml'
+        | 'text/vtt';
+  uri: string;
+}[]
+
+export interface ReactVideoProps extends ReactVideoEvents {
+  source?: ReactVideoSource;
+  drm?: ReactVideoDrm;
+  style?: StyleProp;
+  adTagUrl?: string; // iOS
+  audioOnly?: boolean;
+  automaticallyWaitsToMinimizeStalling?: boolean; // iOS
+  backBufferDurationMs?: number; // Android
+  bufferConfig?: BufferConfig; // Android
+  contentStartTime?: number; // Android
+  controls?: boolean;
+  currentPlaybackTime?: number; // Android
+  disableFocus?: boolean;
+  disableDisconnectError?: boolean; // Android
+  filter?: Filter; // iOS
+  filterEnabled?:	boolean; // iOS
+  focusable?: boolean; 	// Android
+  fullscreen?: boolean; // iOS
+  fullscreenAutorotate?: boolean; // iOS
+  fullscreenOrientation?: 'all' | 'landscape' | 'portrait';	// iOS
+  hideShutterView?: boolean; //	Android
+  ignoreSilentSwitch?: 'inherit' | 'ignore' | 'obey'	// iOS
+  minLoadRetryCount?: number;	// Android
+  maxBitRate?: number;
+  mixWithOthers?: 'inherit' | 'mix' | 'duck'; // iOS
+  muted?: boolean;
+  paused?: boolean;
+  pictureInPicture?: boolean // iOS
+  playInBackground?: boolean;
+  playWhenInactive?: boolean // iOS
+  poster?: string;
+  posterResizeMode?: 'contain' | 'center' | 'cover' | 'none' | 'repeat' | 'stretch';
+  preferredForwardBufferDuration?: number// iOS
+  preventsDisplaySleepDuringVideoPlayback?: boolean;
+  progressUpdateInterval?: number;
+  rate?: number;
+  repeat?: boolean;
+  reportBandwidth?: boolean; //Android
+  resizeMode?: 'none' | 'contain' | 'cover' | 'stretch';
+  selectedAudioTrack?: SelectedTrack;
+  selectedTextTrack?: SelectedTrack;
+  selectedVideoTrack?: SelectedVideoTrack; // android
+  subtitleStyle?: SubtitleStyle // android
+  textTracks?: TextTracks;
+  trackId?: string; // Android
+  useTextureView?: boolean;	// Android
+  useSecureView?: boolean;	// Android
+  volume?: number;
+  localSourceEncryptionKeyScheme?: string;
+}
\ No newline at end of file
diff --git a/src/utils.ts b/src/utils.ts
new file mode 100644
index 00000000..8a32035d
--- /dev/null
+++ b/src/utils.ts
@@ -0,0 +1,37 @@
+import type { Component, RefObject, ComponentClass } from 'react';
+import { Image, UIManager, findNodeHandle } from "react-native";
+import type { ImageSourcePropType } from 'react-native';
+import type { ReactVideoSource } from './types/video';
+
+type Source = ImageSourcePropType | ReactVideoSource;
+
+export function resolveAssetSourceForVideo(source: Source): ReactVideoSource {
+  if (typeof source === 'number') {
+    return {
+      uri: Image.resolveAssetSource(source).uri,
+    };
+  }
+  return source as ReactVideoSource;
+}
+
+export function getReactTag(ref: RefObject | ComponentClass | null>): number {
+  if (!ref.current) {
+    throw new Error("Video Component is not mounted");
+  }
+
+  const reactTag = findNodeHandle(ref.current);
+
+  if (!reactTag) {
+    throw new Error("Cannot find reactTag for Video Component in components tree");
+  }
+
+  return reactTag;
+}
+
+export function getViewManagerConfig(name: string) {
+  if('getViewManagerConfig' in UIManager) {
+    return UIManager.getViewManagerConfig(name);
+  }
+
+  return UIManager[name];
+}
\ No newline at end of file
diff --git a/tsconfig.build.json b/tsconfig.build.json
new file mode 100644
index 00000000..e00766cd
--- /dev/null
+++ b/tsconfig.build.json
@@ -0,0 +1,5 @@
+
+{
+  "extends": "./tsconfig",
+  "exclude": ["examples", "lib"]
+}
\ No newline at end of file
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 00000000..01118ee5
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,33 @@
+{
+  "compilerOptions": {
+    "rootDir": "./src",
+    "outDir": "lib",
+    "paths": {
+      "react-native-video": ["./src/index"]
+    },
+    "composite": true,
+    "declaration": true,
+    "sourceMap": true,
+    "allowUnreachableCode": false,
+    "allowUnusedLabels": false,
+    "esModuleInterop": true,
+    "forceConsistentCasingInFileNames": true,
+    "jsx": "react",
+    "lib": ["esnext"],
+    "module": "esnext",
+    "moduleResolution": "node",
+    "noFallthroughCasesInSwitch": true,
+    "noImplicitReturns": true,
+    "noImplicitUseStrict": false,
+    "noStrictGenericChecks": false,
+    "noUncheckedIndexedAccess": true,
+    "noUnusedLocals": true,
+    "noUnusedParameters": true,
+    "resolveJsonModule": true,
+    "skipLibCheck": true,
+    "strict": true,
+    "target": "esnext",
+    "verbatimModuleSyntax": true
+  },
+  "exclude": ["examples", "lib"]
+}