├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── android ├── build.gradle ├── libs │ ├── armeabi-v7a │ │ ├── libOMX.11.so │ │ ├── libOMX.14.so │ │ ├── libOMX.18.so │ │ ├── libOMX.9.so │ │ ├── libffmpeg.so │ │ ├── libstlport_shared.so │ │ ├── libvao.0.so │ │ ├── libvplayer.so │ │ ├── libvscanner.so │ │ ├── libvvo.0.so │ │ ├── libvvo.7.so │ │ ├── libvvo.8.so │ │ ├── libvvo.9.so │ │ └── libvvo.j.so │ └── x86 │ │ ├── libOMX.14.so │ │ ├── libOMX.18.so │ │ ├── libOMX.9.so │ │ ├── libffmpeg.so │ │ ├── libstlport_shared.so │ │ ├── libvao.0.so │ │ ├── libvplayer.so │ │ ├── libvscanner.so │ │ ├── libvvo.0.so │ │ ├── libvvo.9.so │ │ └── libvvo.j.so └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ ├── com │ ├── remobile │ │ └── video │ │ │ ├── RCTVideoPackage.java │ │ │ ├── RCTVideoView.java │ │ │ └── RCTVideoViewManager.java │ └── yqritc │ │ └── scalablevideoview │ │ ├── PivotPoint.java │ │ ├── ScalableType.java │ │ ├── ScalableVideoView.java │ │ ├── ScaleManager.java │ │ └── Size.java │ └── io │ └── vov │ └── vitamio │ ├── EGL.java │ ├── MediaFile.java │ ├── MediaFormat.java │ ├── MediaMetadataRetriever.java │ ├── MediaPlayer.java │ ├── MediaScanner.java │ ├── MediaScannerClient.java │ ├── Metadata.java │ ├── ThumbnailUtils.java │ ├── VIntent.java │ ├── Vitamio.java │ ├── VitamioLicense.java │ ├── provider │ ├── MediaStore.java │ └── MiniThumbFile.java │ ├── utils │ ├── Base64.java │ ├── CPU.java │ ├── ContextUtils.java │ ├── Crypto.java │ ├── Device.java │ ├── FileUtils.java │ ├── IOUtils.java │ ├── Log.java │ ├── ScreenResolution.java │ └── StringUtils.java │ └── widget │ └── VideoView.java ├── index.js ├── ios ├── RCTVideo.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ └── contents.xcworkspacedata └── RCTVideo │ ├── RCTVideo.h │ ├── RCTVideo.m │ ├── RCTVideoManager.h │ └── RCTVideoManager.m └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | *.[aod] 2 | *.DS_Store 3 | .DS_Store 4 | *Thumbs.db 5 | *.iml 6 | .gradle 7 | .idea 8 | node_modules 9 | npm-debug.log 10 | /android/build 11 | /ios/**/*xcuserdata* 12 | /ios/**/*xcshareddata* 13 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | *.DS_Store 2 | .DS_Store 3 | *Thumbs.db 4 | .gradle 5 | .idea 6 | *.iml 7 | npm-debug.log 8 | node_modules 9 | /android/build 10 | /ios/**/*xcuserdata* 11 | /ios/**/*xcshareddata* 12 | 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2015-2016 YunJiang.Fang <42550564@qq.com> 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | 'Software'), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React Native Video (remobile) 2 | A video player for react-native, support hls 3 | 4 | ## Installation 5 | ```sh 6 | npm install @remobile/react-native-video --save 7 | ``` 8 | ### Installation (iOS) 9 | * Drag RCTVideo.xcodeproj to your project on Xcode. 10 | * Click on your main project file (the one that represents the .xcodeproj) select Build Phases and drag libRCTVideo.a from the Products folder inside the RCTVideo.xcodeproj. 11 | * Look for Header Search Paths and make sure it contains both $(SRCROOT)/../../../react-native/React as recursive. 12 | 13 | ### Installation (Android) 14 | ```gradle 15 | ... 16 | include ':react-native-video' 17 | project(':react-native-video').projectDir = new File(settingsDir, '../node_modules/@remobile/react-native-video/android/RCTVideo') 18 | ``` 19 | 20 | * In `android/app/build.gradle` 21 | 22 | ```gradle 23 | ... 24 | dependencies { 25 | ... 26 | compile project(':react-native-video') 27 | } 28 | ``` 29 | 30 | * register module (in MainApplication.java) 31 | 32 | ```java 33 | ...... 34 | import com.remobile.video.RCTVideoPackage; // <--- import 35 | 36 | ...... 37 | 38 | @Override 39 | protected List getPackages() { 40 | ...... 41 | new RCTVideoPackage(), // <------ add here 42 | ...... 43 | } 44 | 45 | 46 | ## Usage 47 | ### Example 48 | ```js 49 | 'use strict'; 50 | 51 | import React from 'react'; 52 | import { 53 | StyleSheet, 54 | Text, 55 | TouchableOpacity, 56 | View, 57 | } from 'react-native'; 58 | 59 | import Video from '@remobile/react-native-video'; 60 | 61 | class VideoPlayer extends React.Component { 62 | constructor(props) { 63 | super(props); 64 | } 65 | state = { 66 | rate: 1, 67 | volume: 1, 68 | muted: false, 69 | resizeMode: 'stretch', 70 | duration: 0.0, 71 | currentTime: 0.0, 72 | paused: false, 73 | }; 74 | render() { 75 | return ( 76 | 77 | 89 | ); 90 | } 91 | } 92 | module.exports = VideoPlayer; 93 | 94 | var NORMAL_WIDTH = sr.w; 95 | var NORMAL_HEIGHT = NORMAL_WIDTH*2/3; 96 | var FULL_WIDTH = sr.h; 97 | var FULL_HEIGHT = sr.w; 98 | 99 | const styles = StyleSheet.create({ 100 | container: { 101 | flex: 1, 102 | backgroundColor: 'black', 103 | }, 104 | videoNormalFrame: { 105 | position: 'absolute', 106 | top:0, 107 | left: 0, 108 | width: NORMAL_WIDTH, 109 | height: NORMAL_HEIGHT, 110 | }, 111 | }); 112 | ``` 113 | 114 | ## HELP 115 | * look https://github.com/react-native-community/react-native-video 116 | * this repository add support android hls video 117 | 118 | 119 | ### thanks 120 | * this project come from https://github.com/react-native-community/react-native-video 121 | 122 | ### see detail use 123 | * https://github.com/remobile/react-native-template 124 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 23 5 | buildToolsVersion "23.0.1" 6 | 7 | defaultConfig { 8 | minSdkVersion 16 9 | targetSdkVersion 22 10 | versionCode 1 11 | versionName "1.0" 12 | ndk { 13 | abiFilters "armeabi-v7a", "x86" 14 | } 15 | } 16 | sourceSets { 17 | main { 18 | jniLibs.srcDirs = ['libs'] 19 | } 20 | } 21 | buildTypes { 22 | release { 23 | minifyEnabled false 24 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 25 | } 26 | } 27 | } 28 | 29 | dependencies { 30 | compile fileTree(dir: 'libs', include: ['*.jar']) 31 | compile 'com.android.support:appcompat-v7:23.0.1' 32 | compile 'com.facebook.react:react-native:+' 33 | } 34 | -------------------------------------------------------------------------------- /android/libs/armeabi-v7a/libOMX.11.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/remobile/react-native-video/a0c522639769fbb14851639ee23834a295117310/android/libs/armeabi-v7a/libOMX.11.so -------------------------------------------------------------------------------- /android/libs/armeabi-v7a/libOMX.14.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/remobile/react-native-video/a0c522639769fbb14851639ee23834a295117310/android/libs/armeabi-v7a/libOMX.14.so -------------------------------------------------------------------------------- /android/libs/armeabi-v7a/libOMX.18.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/remobile/react-native-video/a0c522639769fbb14851639ee23834a295117310/android/libs/armeabi-v7a/libOMX.18.so -------------------------------------------------------------------------------- /android/libs/armeabi-v7a/libOMX.9.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/remobile/react-native-video/a0c522639769fbb14851639ee23834a295117310/android/libs/armeabi-v7a/libOMX.9.so -------------------------------------------------------------------------------- /android/libs/armeabi-v7a/libffmpeg.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/remobile/react-native-video/a0c522639769fbb14851639ee23834a295117310/android/libs/armeabi-v7a/libffmpeg.so -------------------------------------------------------------------------------- /android/libs/armeabi-v7a/libstlport_shared.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/remobile/react-native-video/a0c522639769fbb14851639ee23834a295117310/android/libs/armeabi-v7a/libstlport_shared.so -------------------------------------------------------------------------------- /android/libs/armeabi-v7a/libvao.0.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/remobile/react-native-video/a0c522639769fbb14851639ee23834a295117310/android/libs/armeabi-v7a/libvao.0.so -------------------------------------------------------------------------------- /android/libs/armeabi-v7a/libvplayer.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/remobile/react-native-video/a0c522639769fbb14851639ee23834a295117310/android/libs/armeabi-v7a/libvplayer.so -------------------------------------------------------------------------------- /android/libs/armeabi-v7a/libvscanner.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/remobile/react-native-video/a0c522639769fbb14851639ee23834a295117310/android/libs/armeabi-v7a/libvscanner.so -------------------------------------------------------------------------------- /android/libs/armeabi-v7a/libvvo.0.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/remobile/react-native-video/a0c522639769fbb14851639ee23834a295117310/android/libs/armeabi-v7a/libvvo.0.so -------------------------------------------------------------------------------- /android/libs/armeabi-v7a/libvvo.7.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/remobile/react-native-video/a0c522639769fbb14851639ee23834a295117310/android/libs/armeabi-v7a/libvvo.7.so -------------------------------------------------------------------------------- /android/libs/armeabi-v7a/libvvo.8.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/remobile/react-native-video/a0c522639769fbb14851639ee23834a295117310/android/libs/armeabi-v7a/libvvo.8.so -------------------------------------------------------------------------------- /android/libs/armeabi-v7a/libvvo.9.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/remobile/react-native-video/a0c522639769fbb14851639ee23834a295117310/android/libs/armeabi-v7a/libvvo.9.so -------------------------------------------------------------------------------- /android/libs/armeabi-v7a/libvvo.j.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/remobile/react-native-video/a0c522639769fbb14851639ee23834a295117310/android/libs/armeabi-v7a/libvvo.j.so -------------------------------------------------------------------------------- /android/libs/x86/libOMX.14.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/remobile/react-native-video/a0c522639769fbb14851639ee23834a295117310/android/libs/x86/libOMX.14.so -------------------------------------------------------------------------------- /android/libs/x86/libOMX.18.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/remobile/react-native-video/a0c522639769fbb14851639ee23834a295117310/android/libs/x86/libOMX.18.so -------------------------------------------------------------------------------- /android/libs/x86/libOMX.9.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/remobile/react-native-video/a0c522639769fbb14851639ee23834a295117310/android/libs/x86/libOMX.9.so -------------------------------------------------------------------------------- /android/libs/x86/libffmpeg.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/remobile/react-native-video/a0c522639769fbb14851639ee23834a295117310/android/libs/x86/libffmpeg.so -------------------------------------------------------------------------------- /android/libs/x86/libstlport_shared.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/remobile/react-native-video/a0c522639769fbb14851639ee23834a295117310/android/libs/x86/libstlport_shared.so -------------------------------------------------------------------------------- /android/libs/x86/libvao.0.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/remobile/react-native-video/a0c522639769fbb14851639ee23834a295117310/android/libs/x86/libvao.0.so -------------------------------------------------------------------------------- /android/libs/x86/libvplayer.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/remobile/react-native-video/a0c522639769fbb14851639ee23834a295117310/android/libs/x86/libvplayer.so -------------------------------------------------------------------------------- /android/libs/x86/libvscanner.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/remobile/react-native-video/a0c522639769fbb14851639ee23834a295117310/android/libs/x86/libvscanner.so -------------------------------------------------------------------------------- /android/libs/x86/libvvo.0.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/remobile/react-native-video/a0c522639769fbb14851639ee23834a295117310/android/libs/x86/libvvo.0.so -------------------------------------------------------------------------------- /android/libs/x86/libvvo.9.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/remobile/react-native-video/a0c522639769fbb14851639ee23834a295117310/android/libs/x86/libvvo.9.so -------------------------------------------------------------------------------- /android/libs/x86/libvvo.j.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/remobile/react-native-video/a0c522639769fbb14851639ee23834a295117310/android/libs/x86/libvvo.j.so -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /android/src/main/java/com/remobile/video/RCTVideoPackage.java: -------------------------------------------------------------------------------- 1 | package com.remobile.video; 2 | 3 | import com.facebook.react.ReactPackage; 4 | import com.facebook.react.bridge.JavaScriptModule; 5 | import com.facebook.react.bridge.NativeModule; 6 | import com.facebook.react.bridge.ReactApplicationContext; 7 | import com.facebook.react.uimanager.ViewManager; 8 | 9 | import java.util.Arrays; 10 | import java.util.Collections; 11 | import java.util.List; 12 | 13 | public class RCTVideoPackage implements ReactPackage { 14 | @Override 15 | public List createNativeModules(ReactApplicationContext reactContext) { 16 | return Collections.emptyList(); 17 | } 18 | 19 | @Override 20 | public List> createJSModules() { 21 | return Collections.emptyList(); 22 | } 23 | 24 | public List createViewManagers(ReactApplicationContext reactContext) { 25 | return Arrays.asList(new RCTVideoViewManager()); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /android/src/main/java/com/remobile/video/RCTVideoView.java: -------------------------------------------------------------------------------- 1 | package com.remobile.video; 2 | 3 | import android.os.Handler; 4 | import android.util.Log; 5 | import com.facebook.react.bridge.Arguments; 6 | import com.facebook.react.bridge.WritableMap; 7 | import com.facebook.react.uimanager.ThemedReactContext; 8 | import com.facebook.react.uimanager.events.RCTEventEmitter; 9 | import com.yqritc.scalablevideoview.ScalableType; 10 | import com.yqritc.scalablevideoview.ScalableVideoView; 11 | 12 | import io.vov.vitamio.MediaPlayer; 13 | import io.vov.vitamio.MediaPlayer.*; 14 | import io.vov.vitamio.Vitamio; 15 | 16 | 17 | public class RCTVideoView extends ScalableVideoView implements OnPreparedListener, OnCompletionListener, OnErrorListener, OnBufferingUpdateListener, OnSeekCompleteListener { 18 | public enum Events { 19 | EVENT_LOAD_START("onVideoLoadStart"), 20 | EVENT_LOAD("onVideoLoad"), 21 | EVENT_ERROR("onVideoError"), 22 | EVENT_PROGRESS("onVideoProgress"), 23 | EVENT_SEEK("onVideoSeek"), 24 | EVENT_END("onVideoEnd"); 25 | 26 | private final String mName; 27 | 28 | Events(final String name) { 29 | mName = name; 30 | } 31 | 32 | @Override 33 | public String toString() { 34 | return mName; 35 | } 36 | } 37 | 38 | public static final String EVENT_PROP_FAST_FORWARD = "canPlayFastForward"; 39 | public static final String EVENT_PROP_SLOW_FORWARD = "canPlaySlowForward"; 40 | public static final String EVENT_PROP_SLOW_REVERSE = "canPlaySlowReverse"; 41 | public static final String EVENT_PROP_REVERSE = "canPlayReverse"; 42 | public static final String EVENT_PROP_STEP_FORWARD = "canStepForward"; 43 | public static final String EVENT_PROP_STEP_BACKWARD = "canStepBackward"; 44 | 45 | public static final String EVENT_PROP_DURATION = "duration"; 46 | public static final String EVENT_PROP_PLAYABLE_DURATION = "playableDuration"; 47 | public static final String EVENT_PROP_CURRENT_TIME = "currentTime"; 48 | public static final String EVENT_PROP_SEEK_TIME = "seekTime"; 49 | 50 | public static final String EVENT_PROP_ERROR = "error"; 51 | public static final String EVENT_PROP_WHAT = "what"; 52 | public static final String EVENT_PROP_EXTRA = "extra"; 53 | 54 | private RCTEventEmitter mEventEmitter; 55 | 56 | private Handler mProgressUpdateHandler = new Handler(); 57 | private Runnable mProgressUpdateRunnable = null; 58 | 59 | private String mSrcUriString = null; 60 | private String mSrcType = "mp4"; 61 | private boolean mSrcIsNetwork = false; 62 | private boolean mSrcIsAsset = false; 63 | private ScalableType mResizeMode = ScalableType.LEFT_TOP; 64 | private boolean mRepeat = false; 65 | private boolean mMuted = false; 66 | private float mVolume = 1.0f; 67 | private float mRate = 1.0f; 68 | private long m_msec = 0; 69 | 70 | private boolean mMediaPlayerValid = false; // True if mMediaPlayer is in prepared, started, or paused state. 71 | 72 | private int mVideoDuration = 0; 73 | private int mVideoBufferedDuration = 0; 74 | 75 | public RCTVideoView(ThemedReactContext themedReactContext) { 76 | super(themedReactContext); 77 | mEventEmitter = themedReactContext.getJSModule(RCTEventEmitter.class); 78 | Vitamio.isInitialized(themedReactContext); 79 | 80 | mProgressUpdateRunnable = new Runnable() { 81 | @Override 82 | public void run() { 83 | 84 | if (mMediaPlayerValid) { 85 | WritableMap event = Arguments.createMap(); 86 | event.putDouble(EVENT_PROP_CURRENT_TIME, getCurrentPosition() / 1000.0); 87 | event.putDouble(EVENT_PROP_PLAYABLE_DURATION, mVideoBufferedDuration / 1000.0); 88 | mEventEmitter.receiveEvent(getId(), Events.EVENT_PROGRESS.toString(), event); 89 | } 90 | mProgressUpdateHandler.postDelayed(mProgressUpdateRunnable, 250); 91 | } 92 | }; 93 | mProgressUpdateHandler.post(mProgressUpdateRunnable); 94 | } 95 | 96 | 97 | public void setSrc(final String uriString, final String type, final boolean isNetwork, final boolean isAsset) { 98 | mSrcUriString = uriString; 99 | mSrcType = type; 100 | mSrcIsNetwork = isNetwork; 101 | mSrcIsAsset = isAsset; 102 | mVideoDuration = 0; 103 | mVideoBufferedDuration = 0; 104 | } 105 | 106 | public void setResizeModeModifier(final ScalableType resizeMode) { 107 | mResizeMode = resizeMode; 108 | 109 | if (mMediaPlayerValid) { 110 | setScalableType(resizeMode); 111 | invalidate(); 112 | } 113 | } 114 | 115 | public void setRepeatModifier(final boolean repeat) { 116 | mRepeat = repeat; 117 | 118 | if (mMediaPlayerValid) { 119 | setLooping(repeat); 120 | } 121 | } 122 | 123 | public void setPausedModifier(final boolean paused) { 124 | mPaused = paused; 125 | 126 | if (!mMediaPlayerValid) { 127 | return; 128 | } 129 | 130 | if (mPaused) { 131 | if (isPlaying()) { 132 | pause(); 133 | } 134 | } else { 135 | if (!isPlaying()) { 136 | start(); 137 | } 138 | } 139 | } 140 | 141 | public void setMutedModifier(final boolean muted) { 142 | mMuted = muted; 143 | 144 | if (!mMediaPlayerValid) { 145 | return; 146 | } 147 | 148 | if (mMuted) { 149 | setVolume(0, 0); 150 | } else { 151 | setVolume(mVolume, mVolume); 152 | } 153 | } 154 | 155 | public void setVolumeModifier(final float volume) { 156 | mVolume = volume; 157 | setMutedModifier(mMuted); 158 | } 159 | 160 | public void setRateModifier(final float rate) { 161 | mRate = rate; 162 | 163 | if (mMediaPlayerValid) { 164 | // TODO: Implement this. 165 | Log.d(RCTVideoViewManager.REACT_CLASS, "Setting playback rate is not yet supported on Android"); 166 | } 167 | } 168 | 169 | public void applyModifiers() { 170 | setResizeModeModifier(mResizeMode); 171 | setRepeatModifier(mRepeat); 172 | setPausedModifier(mPaused); 173 | setMutedModifier(mMuted); 174 | setRateModifier(mRate); 175 | } 176 | 177 | 178 | private void setListeners() { 179 | setOnPreparedListener(this); 180 | setOnCompletionListener(this); 181 | setOnErrorListener(this); 182 | setOnBufferingUpdateListener(this); 183 | setOnSeekCompleteListener(this); 184 | } 185 | 186 | @Override 187 | public void onPrepared(MediaPlayer mp) { 188 | mVideoDuration = (int)mp.getDuration(); 189 | mMediaPlayerValid = true; 190 | mp.setPlaybackSpeed(1.0f); 191 | 192 | WritableMap event = Arguments.createMap(); 193 | event.putDouble(EVENT_PROP_DURATION, mVideoDuration / 1000.0); 194 | event.putDouble(EVENT_PROP_CURRENT_TIME, mp.getCurrentPosition() / 1000.0); 195 | // TODO: Actually check if you can. 196 | event.putBoolean(EVENT_PROP_FAST_FORWARD, true); 197 | event.putBoolean(EVENT_PROP_SLOW_FORWARD, true); 198 | event.putBoolean(EVENT_PROP_SLOW_REVERSE, true); 199 | event.putBoolean(EVENT_PROP_REVERSE, true); 200 | event.putBoolean(EVENT_PROP_FAST_FORWARD, true); 201 | event.putBoolean(EVENT_PROP_STEP_BACKWARD, true); 202 | event.putBoolean(EVENT_PROP_STEP_FORWARD, true); 203 | mEventEmitter.receiveEvent(getId(), Events.EVENT_LOAD.toString(), event); 204 | 205 | applyModifiers(); 206 | } 207 | 208 | @Override 209 | public boolean onError(MediaPlayer mp, int what, int extra) { 210 | WritableMap error = Arguments.createMap(); 211 | error.putInt(EVENT_PROP_WHAT, what); 212 | error.putInt(EVENT_PROP_EXTRA, extra); 213 | WritableMap event = Arguments.createMap(); 214 | event.putMap(EVENT_PROP_ERROR, error); 215 | mEventEmitter.receiveEvent(getId(), Events.EVENT_ERROR.toString(), event); 216 | return true; 217 | } 218 | 219 | @Override 220 | public void onBufferingUpdate(MediaPlayer mp, int percent) { 221 | mVideoBufferedDuration = (int) Math.round((double) (mVideoDuration * percent) / 100.0); 222 | } 223 | 224 | @Override 225 | public void seekTo(long msec) { 226 | if (mMediaPlayerValid) { 227 | m_msec = msec; 228 | super.seekTo(msec); 229 | } 230 | } 231 | 232 | @Override 233 | public void onCompletion(MediaPlayer mp) { 234 | mEventEmitter.receiveEvent(getId(), Events.EVENT_END.toString(), null); 235 | } 236 | 237 | @Override 238 | public void onSeekComplete(MediaPlayer mp) { 239 | WritableMap event = Arguments.createMap(); 240 | event.putDouble(EVENT_PROP_CURRENT_TIME, getCurrentPosition() / 1000.0); 241 | event.putDouble(EVENT_PROP_SEEK_TIME, m_msec / 1000.0); 242 | mEventEmitter.receiveEvent(getId(), Events.EVENT_SEEK.toString(), event); 243 | } 244 | 245 | @Override 246 | protected void onDetachedFromWindow() { 247 | mMediaPlayerValid = false; 248 | super.onDetachedFromWindow(); 249 | } 250 | 251 | @Override 252 | protected void onAttachedToWindow() { 253 | super.onAttachedToWindow(); 254 | 255 | setListeners(); 256 | setVideoPath(mSrcUriString); 257 | requestFocus(); 258 | 259 | WritableMap src = Arguments.createMap(); 260 | src.putString(RCTVideoViewManager.PROP_SRC_URI, mSrcUriString); 261 | src.putString(RCTVideoViewManager.PROP_SRC_TYPE, mSrcType); 262 | src.putBoolean(RCTVideoViewManager.PROP_SRC_IS_NETWORK, mSrcIsNetwork); 263 | WritableMap event = Arguments.createMap(); 264 | event.putMap(RCTVideoViewManager.PROP_SRC, src); 265 | mEventEmitter.receiveEvent(getId(), Events.EVENT_LOAD_START.toString(), event); 266 | } 267 | } 268 | -------------------------------------------------------------------------------- /android/src/main/java/com/remobile/video/RCTVideoViewManager.java: -------------------------------------------------------------------------------- 1 | package com.remobile.video; 2 | 3 | import android.app.Activity; 4 | 5 | import com.remobile.video.RCTVideoView.Events; 6 | import com.facebook.react.bridge.ReadableMap; 7 | import com.facebook.react.common.MapBuilder; 8 | import com.facebook.react.uimanager.annotations.ReactProp; 9 | import com.facebook.react.uimanager.SimpleViewManager; 10 | import com.facebook.react.uimanager.ThemedReactContext; 11 | import com.yqritc.scalablevideoview.ScalableType; 12 | 13 | import javax.annotation.Nullable; 14 | import java.util.Map; 15 | 16 | public class RCTVideoViewManager extends SimpleViewManager { 17 | 18 | public static final String REACT_CLASS = "RCTVideo"; 19 | 20 | public static final String PROP_SRC = "src"; 21 | public static final String PROP_SRC_URI = "uri"; 22 | public static final String PROP_SRC_TYPE = "type"; 23 | public static final String PROP_SRC_IS_NETWORK = "isNetwork"; 24 | public static final String PROP_SRC_IS_ASSET = "isAsset"; 25 | public static final String PROP_RESIZE_MODE = "resizeMode"; 26 | public static final String PROP_REPEAT = "repeat"; 27 | public static final String PROP_PAUSED = "paused"; 28 | public static final String PROP_MUTED = "muted"; 29 | public static final String PROP_VOLUME = "volume"; 30 | public static final String PROP_SEEK = "seek"; 31 | public static final String PROP_RATE = "rate"; 32 | 33 | 34 | @Override 35 | public String getName() { 36 | return REACT_CLASS; 37 | } 38 | 39 | @Override 40 | protected RCTVideoView createViewInstance(ThemedReactContext themedReactContext) { 41 | return new RCTVideoView(themedReactContext); 42 | } 43 | 44 | @Override 45 | @Nullable 46 | public Map getExportedCustomDirectEventTypeConstants() { 47 | MapBuilder.Builder builder = MapBuilder.builder(); 48 | for (Events event : Events.values()) { 49 | builder.put(event.toString(), MapBuilder.of("registrationName", event.toString())); 50 | } 51 | return builder.build(); 52 | } 53 | 54 | @Override 55 | @Nullable 56 | public Map getExportedViewConstants() { 57 | return MapBuilder.of( 58 | "ScaleNone", Integer.toString(ScalableType.LEFT_TOP.ordinal()), 59 | "ScaleToFill", Integer.toString(ScalableType.FIT_XY.ordinal()), 60 | "ScaleAspectFit", Integer.toString(ScalableType.FIT_CENTER.ordinal()), 61 | "ScaleAspectFill", Integer.toString(ScalableType.CENTER_CROP.ordinal()) 62 | ); 63 | } 64 | 65 | @ReactProp(name = PROP_SRC) 66 | public void setSrc(final RCTVideoView videoView, @Nullable ReadableMap src) { 67 | videoView.setSrc( 68 | src.getString(PROP_SRC_URI), 69 | src.getString(PROP_SRC_TYPE), 70 | src.getBoolean(PROP_SRC_IS_NETWORK), 71 | src.getBoolean(PROP_SRC_IS_ASSET) 72 | ); 73 | } 74 | 75 | @ReactProp(name = PROP_RESIZE_MODE) 76 | public void setResizeMode(final RCTVideoView videoView, final String resizeModeOrdinalString) { 77 | videoView.setResizeModeModifier(ScalableType.values()[Integer.parseInt(resizeModeOrdinalString)]); 78 | } 79 | 80 | @ReactProp(name = PROP_REPEAT, defaultBoolean = false) 81 | public void setRepeat(final RCTVideoView videoView, final boolean repeat) { 82 | videoView.setRepeatModifier(repeat); 83 | } 84 | 85 | @ReactProp(name = PROP_PAUSED, defaultBoolean = false) 86 | public void setPaused(final RCTVideoView videoView, final boolean paused) { 87 | videoView.setPausedModifier(paused); 88 | } 89 | 90 | @ReactProp(name = PROP_MUTED, defaultBoolean = false) 91 | public void setMuted(final RCTVideoView videoView, final boolean muted) { 92 | videoView.setMutedModifier(muted); 93 | } 94 | 95 | @ReactProp(name = PROP_VOLUME, defaultFloat = 1.0f) 96 | public void setVolume(final RCTVideoView videoView, final float volume) { 97 | videoView.setVolumeModifier(volume); 98 | } 99 | 100 | @ReactProp(name = PROP_SEEK) 101 | public void setSeek(final RCTVideoView videoView, final float seek) { 102 | videoView.seekTo(Math.round(seek * 1000.0f)); 103 | } 104 | 105 | @ReactProp(name = PROP_RATE) 106 | public void setRate(final RCTVideoView videoView, final float rate) { 107 | videoView.setRateModifier(rate); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /android/src/main/java/com/yqritc/scalablevideoview/PivotPoint.java: -------------------------------------------------------------------------------- 1 | package com.yqritc.scalablevideoview; 2 | 3 | /** 4 | * Created by yqritc on 2015/06/14. 5 | */ 6 | public enum PivotPoint { 7 | LEFT_TOP, 8 | LEFT_CENTER, 9 | LEFT_BOTTOM, 10 | CENTER_TOP, 11 | CENTER, 12 | CENTER_BOTTOM, 13 | RIGHT_TOP, 14 | RIGHT_CENTER, 15 | RIGHT_BOTTOM 16 | } 17 | -------------------------------------------------------------------------------- /android/src/main/java/com/yqritc/scalablevideoview/ScalableType.java: -------------------------------------------------------------------------------- 1 | package com.yqritc.scalablevideoview; 2 | 3 | /** 4 | * Created by yqritc on 2015/06/12. 5 | */ 6 | public enum ScalableType { 7 | NONE, 8 | 9 | FIT_XY, 10 | FIT_START, 11 | FIT_CENTER, 12 | FIT_END, 13 | 14 | LEFT_TOP, 15 | LEFT_CENTER, 16 | LEFT_BOTTOM, 17 | CENTER_TOP, 18 | CENTER, 19 | CENTER_BOTTOM, 20 | RIGHT_TOP, 21 | RIGHT_CENTER, 22 | RIGHT_BOTTOM, 23 | 24 | LEFT_TOP_CROP, 25 | LEFT_CENTER_CROP, 26 | LEFT_BOTTOM_CROP, 27 | CENTER_TOP_CROP, 28 | CENTER_CROP, 29 | CENTER_BOTTOM_CROP, 30 | RIGHT_TOP_CROP, 31 | RIGHT_CENTER_CROP, 32 | RIGHT_BOTTOM_CROP, 33 | 34 | START_INSIDE, 35 | CENTER_INSIDE, 36 | END_INSIDE 37 | } 38 | -------------------------------------------------------------------------------- /android/src/main/java/com/yqritc/scalablevideoview/ScaleManager.java: -------------------------------------------------------------------------------- 1 | package com.yqritc.scalablevideoview; 2 | 3 | import android.graphics.Matrix; 4 | 5 | /** 6 | * Created by yqritc on 2015/06/12. 7 | */ 8 | public class ScaleManager { 9 | 10 | private Size mViewSize; 11 | private Size mVideoSize; 12 | 13 | public ScaleManager(Size viewSize, Size videoSize) { 14 | mViewSize = viewSize; 15 | mVideoSize = videoSize; 16 | } 17 | 18 | public Matrix getScaleMatrix(ScalableType scalableType) { 19 | switch (scalableType) { 20 | case NONE: 21 | return getNoScale(); 22 | 23 | case FIT_XY: 24 | return fitXY(); 25 | case FIT_CENTER: 26 | return fitCenter(); 27 | case FIT_START: 28 | return fitStart(); 29 | case FIT_END: 30 | return fitEnd(); 31 | 32 | case LEFT_TOP: 33 | return getOriginalScale(PivotPoint.LEFT_TOP); 34 | case LEFT_CENTER: 35 | return getOriginalScale(PivotPoint.LEFT_CENTER); 36 | case LEFT_BOTTOM: 37 | return getOriginalScale(PivotPoint.LEFT_BOTTOM); 38 | case CENTER_TOP: 39 | return getOriginalScale(PivotPoint.CENTER_TOP); 40 | case CENTER: 41 | return getOriginalScale(PivotPoint.CENTER); 42 | case CENTER_BOTTOM: 43 | return getOriginalScale(PivotPoint.CENTER_BOTTOM); 44 | case RIGHT_TOP: 45 | return getOriginalScale(PivotPoint.RIGHT_TOP); 46 | case RIGHT_CENTER: 47 | return getOriginalScale(PivotPoint.RIGHT_CENTER); 48 | case RIGHT_BOTTOM: 49 | return getOriginalScale(PivotPoint.RIGHT_BOTTOM); 50 | 51 | case LEFT_TOP_CROP: 52 | return getCropScale(PivotPoint.LEFT_TOP); 53 | case LEFT_CENTER_CROP: 54 | return getCropScale(PivotPoint.LEFT_CENTER); 55 | case LEFT_BOTTOM_CROP: 56 | return getCropScale(PivotPoint.LEFT_BOTTOM); 57 | case CENTER_TOP_CROP: 58 | return getCropScale(PivotPoint.CENTER_TOP); 59 | case CENTER_CROP: 60 | return getCropScale(PivotPoint.CENTER); 61 | case CENTER_BOTTOM_CROP: 62 | return getCropScale(PivotPoint.CENTER_BOTTOM); 63 | case RIGHT_TOP_CROP: 64 | return getCropScale(PivotPoint.RIGHT_TOP); 65 | case RIGHT_CENTER_CROP: 66 | return getCropScale(PivotPoint.RIGHT_CENTER); 67 | case RIGHT_BOTTOM_CROP: 68 | return getCropScale(PivotPoint.RIGHT_BOTTOM); 69 | 70 | case START_INSIDE: 71 | return startInside(); 72 | case CENTER_INSIDE: 73 | return centerInside(); 74 | case END_INSIDE: 75 | return endInside(); 76 | 77 | default: 78 | return null; 79 | } 80 | } 81 | 82 | private Matrix getMatrix(float sx, float sy, float px, float py) { 83 | Matrix matrix = new Matrix(); 84 | matrix.setScale(sx, sy, px, py); 85 | return matrix; 86 | } 87 | 88 | private Matrix getMatrix(float sx, float sy, PivotPoint pivotPoint) { 89 | switch (pivotPoint) { 90 | case LEFT_TOP: 91 | return getMatrix(sx, sy, 0, 0); 92 | case LEFT_CENTER: 93 | return getMatrix(sx, sy, 0, mViewSize.getHeight() / 2f); 94 | case LEFT_BOTTOM: 95 | return getMatrix(sx, sy, 0, mViewSize.getHeight()); 96 | case CENTER_TOP: 97 | return getMatrix(sx, sy, mViewSize.getWidth() / 2f, 0); 98 | case CENTER: 99 | return getMatrix(sx, sy, mViewSize.getWidth() / 2f, mViewSize.getHeight() / 2f); 100 | case CENTER_BOTTOM: 101 | return getMatrix(sx, sy, mViewSize.getWidth() / 2f, mViewSize.getHeight()); 102 | case RIGHT_TOP: 103 | return getMatrix(sx, sy, mViewSize.getWidth(), 0); 104 | case RIGHT_CENTER: 105 | return getMatrix(sx, sy, mViewSize.getWidth(), mViewSize.getHeight() / 2f); 106 | case RIGHT_BOTTOM: 107 | return getMatrix(sx, sy, mViewSize.getWidth(), mViewSize.getHeight()); 108 | default: 109 | throw new IllegalArgumentException("Illegal PivotPoint"); 110 | } 111 | } 112 | 113 | private Matrix getNoScale() { 114 | float sx = mVideoSize.getWidth() / (float) mViewSize.getWidth(); 115 | float sy = mVideoSize.getHeight() / (float) mViewSize.getHeight(); 116 | return getMatrix(sx, sy, PivotPoint.LEFT_TOP); 117 | } 118 | 119 | private Matrix getFitScale(PivotPoint pivotPoint) { 120 | float sx = (float) mViewSize.getWidth() / mVideoSize.getWidth(); 121 | float sy = (float) mViewSize.getHeight() / mVideoSize.getHeight(); 122 | float minScale = Math.min(sx, sy); 123 | sx = minScale / sx; 124 | sy = minScale / sy; 125 | return getMatrix(sx, sy, pivotPoint); 126 | } 127 | 128 | private Matrix fitXY() { 129 | return getMatrix(1, 1, PivotPoint.LEFT_TOP); 130 | } 131 | 132 | private Matrix fitStart() { 133 | return getFitScale(PivotPoint.LEFT_TOP); 134 | } 135 | 136 | private Matrix fitCenter() { 137 | return getFitScale(PivotPoint.CENTER); 138 | } 139 | 140 | private Matrix fitEnd() { 141 | return getFitScale(PivotPoint.RIGHT_BOTTOM); 142 | } 143 | 144 | private Matrix getOriginalScale(PivotPoint pivotPoint) { 145 | float sx = mVideoSize.getWidth() / (float) mViewSize.getWidth(); 146 | float sy = mVideoSize.getHeight() / (float) mViewSize.getHeight(); 147 | return getMatrix(sx, sy, pivotPoint); 148 | } 149 | 150 | private Matrix getCropScale(PivotPoint pivotPoint) { 151 | float sx = (float) mViewSize.getWidth() / mVideoSize.getWidth(); 152 | float sy = (float) mViewSize.getHeight() / mVideoSize.getHeight(); 153 | float maxScale = Math.max(sx, sy); 154 | sx = maxScale / sx; 155 | sy = maxScale / sy; 156 | return getMatrix(sx, sy, pivotPoint); 157 | } 158 | 159 | private Matrix startInside() { 160 | if (mVideoSize.getHeight() <= mViewSize.getWidth() 161 | && mVideoSize.getHeight() <= mViewSize.getHeight()) { 162 | // video is smaller than view size 163 | return getOriginalScale(PivotPoint.LEFT_TOP); 164 | } else { 165 | // either of width or height of the video is larger than view size 166 | return fitStart(); 167 | } 168 | } 169 | 170 | private Matrix centerInside() { 171 | if (mVideoSize.getHeight() <= mViewSize.getWidth() 172 | && mVideoSize.getHeight() <= mViewSize.getHeight()) { 173 | // video is smaller than view size 174 | return getOriginalScale(PivotPoint.CENTER); 175 | } else { 176 | // either of width or height of the video is larger than view size 177 | return fitCenter(); 178 | } 179 | } 180 | 181 | private Matrix endInside() { 182 | if (mVideoSize.getHeight() <= mViewSize.getWidth() 183 | && mVideoSize.getHeight() <= mViewSize.getHeight()) { 184 | // video is smaller than view size 185 | return getOriginalScale(PivotPoint.RIGHT_BOTTOM); 186 | } else { 187 | // either of width or height of the video is larger than view size 188 | return fitEnd(); 189 | } 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /android/src/main/java/com/yqritc/scalablevideoview/Size.java: -------------------------------------------------------------------------------- 1 | package com.yqritc.scalablevideoview; 2 | 3 | /** 4 | * Created by yqritc on 2015/06/12. 5 | */ 6 | public class Size { 7 | 8 | private int mWidth; 9 | private int mHeight; 10 | 11 | public Size(int width, int height) { 12 | mWidth = width; 13 | mHeight = height; 14 | } 15 | 16 | public int getWidth() { 17 | return mWidth; 18 | } 19 | 20 | public int getHeight() { 21 | return mHeight; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /android/src/main/java/io/vov/vitamio/EGL.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 YIXIA.COM 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.vov.vitamio; 18 | 19 | import javax.microedition.khronos.egl.EGL10; 20 | import javax.microedition.khronos.egl.EGL11; 21 | import javax.microedition.khronos.egl.EGLConfig; 22 | import javax.microedition.khronos.egl.EGLContext; 23 | import javax.microedition.khronos.egl.EGLDisplay; 24 | import javax.microedition.khronos.egl.EGLSurface; 25 | import javax.microedition.khronos.opengles.GL; 26 | import android.util.Log; 27 | import android.view.Surface; 28 | 29 | 30 | /** 31 | * DON'T MODIFY THIS FILE IF YOU'RE NOT FAMILIAR WITH EGL, IT'S USED BY NATIVE CODE!!! 32 | */ 33 | public class EGL { 34 | private EGL10 mEgl; 35 | private EGLDisplay mEglDisplay; 36 | private EGLSurface mEglSurface; 37 | private EGLConfig mEglConfig; 38 | private EGLContext mEglContext; 39 | private EGLConfigChooser mEGLConfigChooser; 40 | private EGLContextFactory mEGLContextFactory; 41 | private EGLWindowSurfaceFactory mEGLWindowSurfaceFactory; 42 | 43 | public EGL() { 44 | mEGLConfigChooser = new SimpleEGLConfigChooser(); 45 | mEGLContextFactory = new EGLContextFactory(); 46 | mEGLWindowSurfaceFactory = new EGLWindowSurfaceFactory(); 47 | } 48 | 49 | public boolean initialize(Surface surface) { 50 | start(); 51 | return createSurface(surface) != null; 52 | } 53 | 54 | public void release() { 55 | destroySurface(); 56 | finish(); 57 | } 58 | 59 | public void start() { 60 | mEgl = (EGL10) EGLContext.getEGL(); 61 | mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); 62 | 63 | if (mEglDisplay == EGL10.EGL_NO_DISPLAY) { 64 | throw new RuntimeException("eglGetDisplay failed"); 65 | } 66 | 67 | int[] version = new int[2]; 68 | if (!mEgl.eglInitialize(mEglDisplay, version)) { 69 | throw new RuntimeException("eglInitialize failed"); 70 | } 71 | mEglConfig = mEGLConfigChooser.chooseConfig(mEgl, mEglDisplay); 72 | 73 | mEglContext = mEGLContextFactory.createContext(mEgl, mEglDisplay, mEglConfig); 74 | if (mEglContext == null || mEglContext == EGL10.EGL_NO_CONTEXT) { 75 | mEglContext = null; 76 | throwEglException("createContext"); 77 | } 78 | 79 | mEglSurface = null; 80 | } 81 | 82 | public GL createSurface(Surface surface) { 83 | if (mEgl == null) 84 | throw new RuntimeException("egl not initialized"); 85 | if (mEglDisplay == null) 86 | throw new RuntimeException("eglDisplay not initialized"); 87 | if (mEglConfig == null) 88 | throw new RuntimeException("mEglConfig not initialized"); 89 | 90 | if (mEglSurface != null && mEglSurface != EGL10.EGL_NO_SURFACE) { 91 | mEgl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT); 92 | mEGLWindowSurfaceFactory.destroySurface(mEgl, mEglDisplay, mEglSurface); 93 | } 94 | 95 | mEglSurface = mEGLWindowSurfaceFactory.createWindowSurface(mEgl, mEglDisplay, mEglConfig, surface); 96 | 97 | if (mEglSurface == null || mEglSurface == EGL10.EGL_NO_SURFACE) { 98 | int error = mEgl.eglGetError(); 99 | if (error == EGL10.EGL_BAD_NATIVE_WINDOW) { 100 | Log.e("EglHelper", "createWindowSurface returned EGL_BAD_NATIVE_WINDOW."); 101 | return null; 102 | } 103 | throwEglException("createWindowSurface", error); 104 | } 105 | 106 | if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) { 107 | throwEglException("eglMakeCurrent"); 108 | } 109 | 110 | GL gl = mEglContext.getGL(); 111 | 112 | return gl; 113 | } 114 | 115 | public boolean swap() { 116 | if (!mEgl.eglSwapBuffers(mEglDisplay, mEglSurface)) { 117 | int error = mEgl.eglGetError(); 118 | switch (error) { 119 | case EGL11.EGL_CONTEXT_LOST: 120 | return false; 121 | case EGL10.EGL_BAD_NATIVE_WINDOW: 122 | Log.e("EglHelper", "eglSwapBuffers returned EGL_BAD_NATIVE_WINDOW. tid=" + Thread.currentThread().getId()); 123 | break; 124 | case EGL10.EGL_BAD_SURFACE: 125 | Log.e("EglHelper", "eglSwapBuffers returned EGL_BAD_SURFACE. tid=" + Thread.currentThread().getId()); 126 | return false; 127 | default: 128 | throwEglException("eglSwapBuffers", error); 129 | } 130 | } 131 | return true; 132 | } 133 | 134 | public void destroySurface() { 135 | if (mEglSurface != null && mEglSurface != EGL10.EGL_NO_SURFACE) { 136 | mEgl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT); 137 | mEGLWindowSurfaceFactory.destroySurface(mEgl, mEglDisplay, mEglSurface); 138 | mEglSurface = null; 139 | } 140 | } 141 | 142 | public void finish() { 143 | if (mEglContext != null) { 144 | mEGLContextFactory.destroyContext(mEgl, mEglDisplay, mEglContext); 145 | mEglContext = null; 146 | } 147 | if (mEglDisplay != null) { 148 | mEgl.eglTerminate(mEglDisplay); 149 | mEglDisplay = null; 150 | } 151 | } 152 | 153 | private void throwEglException(String function) { 154 | throwEglException(function, mEgl.eglGetError()); 155 | } 156 | 157 | private void throwEglException(String function, int error) { 158 | String message = String.format("%s failed: %x", function, error); 159 | Log.e("EglHelper", "throwEglException tid=" + Thread.currentThread().getId() + " " + message); 160 | throw new RuntimeException(message); 161 | } 162 | 163 | private static class EGLWindowSurfaceFactory { 164 | 165 | public EGLSurface createWindowSurface(EGL10 egl, EGLDisplay display, EGLConfig config, Object nativeWindow) { 166 | return egl.eglCreateWindowSurface(display, config, nativeWindow, null); 167 | } 168 | 169 | public void destroySurface(EGL10 egl, EGLDisplay display, EGLSurface surface) { 170 | egl.eglDestroySurface(display, surface); 171 | } 172 | } 173 | 174 | private class EGLContextFactory { 175 | private int EGL_CONTEXT_CLIENT_VERSION = 0x3098; 176 | 177 | public EGLContext createContext(EGL10 egl, EGLDisplay display, EGLConfig config) { 178 | int[] attrib_list = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE}; 179 | 180 | return egl.eglCreateContext(display, config, EGL10.EGL_NO_CONTEXT, attrib_list); 181 | } 182 | 183 | public void destroyContext(EGL10 egl, EGLDisplay display, EGLContext context) { 184 | if (!egl.eglDestroyContext(display, context)) { 185 | Log.e("DefaultContextFactory", "display:" + display + " context: " + context); 186 | throw new RuntimeException("eglDestroyContext failed: "); 187 | } 188 | } 189 | } 190 | 191 | private abstract class EGLConfigChooser { 192 | protected int[] mConfigSpec; 193 | 194 | public EGLConfigChooser(int[] configSpec) { 195 | mConfigSpec = filterConfigSpec(configSpec); 196 | } 197 | 198 | public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) { 199 | int[] num_config = new int[1]; 200 | if (!egl.eglChooseConfig(display, mConfigSpec, null, 0, num_config)) { 201 | throw new IllegalArgumentException("eglChooseConfig failed"); 202 | } 203 | 204 | int numConfigs = num_config[0]; 205 | 206 | if (numConfigs <= 0) { 207 | throw new IllegalArgumentException("No configs match configSpec"); 208 | } 209 | 210 | EGLConfig[] configs = new EGLConfig[numConfigs]; 211 | if (!egl.eglChooseConfig(display, mConfigSpec, configs, numConfigs, num_config)) { 212 | throw new IllegalArgumentException("eglChooseConfig#2 failed"); 213 | } 214 | EGLConfig config = chooseConfig(egl, display, configs); 215 | if (config == null) { 216 | throw new IllegalArgumentException("No config chosen"); 217 | } 218 | return config; 219 | } 220 | 221 | abstract EGLConfig chooseConfig(EGL10 egl, EGLDisplay display, EGLConfig[] configs); 222 | 223 | private int[] filterConfigSpec(int[] configSpec) { 224 | int len = configSpec.length; 225 | int[] newConfigSpec = new int[len + 2]; 226 | System.arraycopy(configSpec, 0, newConfigSpec, 0, len - 1); 227 | newConfigSpec[len - 1] = EGL10.EGL_RENDERABLE_TYPE; 228 | newConfigSpec[len] = 4; /* EGL_OPENGL_ES2_BIT */ 229 | newConfigSpec[len + 1] = EGL10.EGL_NONE; 230 | return newConfigSpec; 231 | } 232 | } 233 | 234 | private class ComponentSizeChooser extends EGLConfigChooser { 235 | protected int mRedSize; 236 | protected int mGreenSize; 237 | protected int mBlueSize; 238 | protected int mAlphaSize; 239 | protected int mDepthSize; 240 | protected int mStencilSize; 241 | private int[] mValue; 242 | 243 | public ComponentSizeChooser(int redSize, int greenSize, int blueSize, int alphaSize, int depthSize, int stencilSize) { 244 | super(new int[]{EGL10.EGL_RED_SIZE, redSize, EGL10.EGL_GREEN_SIZE, greenSize, EGL10.EGL_BLUE_SIZE, blueSize, EGL10.EGL_ALPHA_SIZE, alphaSize, EGL10.EGL_DEPTH_SIZE, depthSize, EGL10.EGL_STENCIL_SIZE, stencilSize, EGL10.EGL_NONE}); 245 | mValue = new int[1]; 246 | mRedSize = redSize; 247 | mGreenSize = greenSize; 248 | mBlueSize = blueSize; 249 | mAlphaSize = alphaSize; 250 | mDepthSize = depthSize; 251 | mStencilSize = stencilSize; 252 | } 253 | 254 | @Override 255 | public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display, EGLConfig[] configs) { 256 | for (EGLConfig config : configs) { 257 | int d = findConfigAttrib(egl, display, config, EGL10.EGL_DEPTH_SIZE, 0); 258 | int s = findConfigAttrib(egl, display, config, EGL10.EGL_STENCIL_SIZE, 0); 259 | if ((d >= mDepthSize) && (s >= mStencilSize)) { 260 | int r = findConfigAttrib(egl, display, config, EGL10.EGL_RED_SIZE, 0); 261 | int g = findConfigAttrib(egl, display, config, EGL10.EGL_GREEN_SIZE, 0); 262 | int b = findConfigAttrib(egl, display, config, EGL10.EGL_BLUE_SIZE, 0); 263 | int a = findConfigAttrib(egl, display, config, EGL10.EGL_ALPHA_SIZE, 0); 264 | if ((r == mRedSize) && (g == mGreenSize) && (b == mBlueSize) && (a == mAlphaSize)) { 265 | return config; 266 | } 267 | } 268 | } 269 | return null; 270 | } 271 | 272 | private int findConfigAttrib(EGL10 egl, EGLDisplay display, EGLConfig config, int attribute, int defaultValue) { 273 | 274 | if (egl.eglGetConfigAttrib(display, config, attribute, mValue)) { 275 | return mValue[0]; 276 | } 277 | return defaultValue; 278 | } 279 | } 280 | 281 | private class SimpleEGLConfigChooser extends ComponentSizeChooser { 282 | public SimpleEGLConfigChooser() { 283 | super(5, 6, 5, 0, 0, 0); 284 | } 285 | } 286 | 287 | } 288 | -------------------------------------------------------------------------------- /android/src/main/java/io/vov/vitamio/MediaFile.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2006 The Android Open Source Project 3 | * Copyright (C) 2013 YIXIA.COM 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package io.vov.vitamio; 19 | 20 | import java.util.HashMap; 21 | import java.util.Iterator; 22 | 23 | 24 | public class MediaFile { 25 | protected final static String sFileExtensions; 26 | 27 | public static final int FILE_TYPE_MP3 = 1; 28 | public static final int FILE_TYPE_M4A = 2; 29 | public static final int FILE_TYPE_WAV = 3; 30 | public static final int FILE_TYPE_AMR = 4; 31 | public static final int FILE_TYPE_AWB = 5; 32 | public static final int FILE_TYPE_WMA = 6; 33 | public static final int FILE_TYPE_OGG = 7; 34 | public static final int FILE_TYPE_AAC = 8; 35 | public static final int FILE_TYPE_MKA = 9; 36 | public static final int FILE_TYPE_MID = 10; 37 | public static final int FILE_TYPE_SMF = 11; 38 | public static final int FILE_TYPE_IMY = 12; 39 | public static final int FILE_TYPE_APE = 13; 40 | public static final int FILE_TYPE_FLAC = 14; 41 | private static final int FIRST_AUDIO_FILE_TYPE = FILE_TYPE_MP3; 42 | private static final int LAST_AUDIO_FILE_TYPE = FILE_TYPE_FLAC; 43 | 44 | public static final int FILE_TYPE_MP4 = 701; 45 | public static final int FILE_TYPE_M4V = 702; 46 | public static final int FILE_TYPE_3GPP = 703; 47 | public static final int FILE_TYPE_3GPP2 = 704; 48 | public static final int FILE_TYPE_WMV = 705; 49 | public static final int FILE_TYPE_ASF = 706; 50 | public static final int FILE_TYPE_MKV = 707; 51 | public static final int FILE_TYPE_MP2TS = 708; 52 | public static final int FILE_TYPE_FLV = 709; 53 | public static final int FILE_TYPE_MOV = 710; 54 | public static final int FILE_TYPE_RM = 711; 55 | public static final int FILE_TYPE_DVD = 712; 56 | public static final int FILE_TYPE_DIVX = 713; 57 | public static final int FILE_TYPE_OGV = 714; 58 | public static final int FILE_TYPE_VIVO = 715; 59 | public static final int FILE_TYPE_WTV = 716; 60 | public static final int FILE_TYPE_AVS = 717; 61 | public static final int FILE_TYPE_SWF = 718; 62 | public static final int FILE_TYPE_RAW = 719; 63 | private static final int FIRST_VIDEO_FILE_TYPE = FILE_TYPE_MP4; 64 | private static final int LAST_VIDEO_FILE_TYPE = FILE_TYPE_RAW; 65 | 66 | protected static class MediaFileType { 67 | int fileType; 68 | String mimeType; 69 | 70 | MediaFileType(int fileType, String mimeType) { 71 | this.fileType = fileType; 72 | this.mimeType = mimeType; 73 | } 74 | } 75 | 76 | private static HashMap sFileTypeMap = new HashMap(); 77 | private static HashMap sMimeTypeMap = new HashMap(); 78 | 79 | static void addFileType(String extension, int fileType, String mimeType) { 80 | sFileTypeMap.put(extension, new MediaFileType(fileType, mimeType)); 81 | sMimeTypeMap.put(mimeType, Integer.valueOf(fileType)); 82 | } 83 | 84 | static { 85 | // addFileType("MP3", FILE_TYPE_MP3, "audio/mpeg"); 86 | // addFileType("M4A", FILE_TYPE_M4A, "audio/mp4"); 87 | // addFileType("WAV", FILE_TYPE_WAV, "audio/x-wav"); 88 | // addFileType("AMR", FILE_TYPE_AMR, "audio/amr"); 89 | // addFileType("AWB", FILE_TYPE_AWB, "audio/amr-wb"); 90 | // addFileType("WMA", FILE_TYPE_WMA, "audio/x-ms-wma"); 91 | // addFileType("OGG", FILE_TYPE_OGG, "application/ogg"); 92 | // addFileType("OGA", FILE_TYPE_OGG, "application/ogg"); 93 | // addFileType("AAC", FILE_TYPE_AAC, "audio/aac"); 94 | // addFileType("MKA", FILE_TYPE_MKA, "audio/x-matroska"); 95 | // addFileType("MID", FILE_TYPE_MID, "audio/midi"); 96 | // addFileType("MIDI", FILE_TYPE_MID, "audio/midi"); 97 | // addFileType("XMF", FILE_TYPE_MID, "audio/midi"); 98 | // addFileType("RTTTL", FILE_TYPE_MID, "audio/midi"); 99 | // addFileType("SMF", FILE_TYPE_SMF, "audio/sp-midi"); 100 | // addFileType("IMY", FILE_TYPE_IMY, "audio/imelody"); 101 | // addFileType("RTX", FILE_TYPE_MID, "audio/midi"); 102 | // addFileType("OTA", FILE_TYPE_MID, "audio/midi"); 103 | // addFileType("APE", FILE_TYPE_APE, "audio/x-ape"); 104 | // addFileType("FLAC", FILE_TYPE_FLAC, "audio/flac"); 105 | 106 | addFileType("M1V", FILE_TYPE_MP4, "video/mpeg"); 107 | addFileType("MP2", FILE_TYPE_MP4, "video/mpeg"); 108 | addFileType("MPE", FILE_TYPE_MP4, "video/mpeg"); 109 | addFileType("MPG", FILE_TYPE_MP4, "video/mpeg"); 110 | addFileType("MPEG", FILE_TYPE_MP4, "video/mpeg"); 111 | addFileType("MP4", FILE_TYPE_MP4, "video/mp4"); 112 | addFileType("M4V", FILE_TYPE_M4V, "video/mp4"); 113 | addFileType("3GP", FILE_TYPE_3GPP, "video/3gpp"); 114 | addFileType("3GPP", FILE_TYPE_3GPP, "video/3gpp"); 115 | addFileType("3G2", FILE_TYPE_3GPP2, "video/3gpp2"); 116 | addFileType("3GPP2", FILE_TYPE_3GPP2, "video/3gpp2"); 117 | addFileType("MKV", FILE_TYPE_MKV, "video/x-matroska"); 118 | addFileType("WEBM", FILE_TYPE_MKV, "video/x-matroska"); 119 | addFileType("MTS", FILE_TYPE_MP2TS, "video/mp2ts"); 120 | addFileType("TS", FILE_TYPE_MP2TS, "video/mp2ts"); 121 | addFileType("TP", FILE_TYPE_MP2TS, "video/mp2ts"); 122 | addFileType("WMV", FILE_TYPE_WMV, "video/x-ms-wmv"); 123 | addFileType("ASF", FILE_TYPE_ASF, "video/x-ms-asf"); 124 | addFileType("ASX", FILE_TYPE_ASF, "video/x-ms-asf"); 125 | addFileType("FLV", FILE_TYPE_FLV, "video/x-flv"); 126 | addFileType("F4V", FILE_TYPE_FLV, "video/x-flv"); 127 | addFileType("HLV", FILE_TYPE_FLV, "video/x-flv"); 128 | addFileType("MOV", FILE_TYPE_MOV, "video/quicktime"); 129 | addFileType("QT", FILE_TYPE_MOV, "video/quicktime"); 130 | addFileType("RM", FILE_TYPE_RM, "video/x-pn-realvideo"); 131 | addFileType("RMVB", FILE_TYPE_RM, "video/x-pn-realvideo"); 132 | addFileType("VOB", FILE_TYPE_DVD, "video/dvd"); 133 | addFileType("DAT", FILE_TYPE_DVD, "video/dvd"); 134 | addFileType("AVI", FILE_TYPE_DIVX, "video/x-divx"); 135 | addFileType("OGV", FILE_TYPE_OGV, "video/ogg"); 136 | addFileType("OGG", FILE_TYPE_OGV, "video/ogg"); 137 | addFileType("VIV", FILE_TYPE_VIVO, "video/vnd.vivo"); 138 | addFileType("VIVO", FILE_TYPE_VIVO, "video/vnd.vivo"); 139 | addFileType("WTV", FILE_TYPE_WTV, "video/wtv"); 140 | addFileType("AVS", FILE_TYPE_AVS, "video/avs-video"); 141 | addFileType("SWF", FILE_TYPE_SWF, "video/x-shockwave-flash"); 142 | addFileType("YUV", FILE_TYPE_RAW, "video/x-raw-yuv"); 143 | 144 | StringBuilder builder = new StringBuilder(); 145 | Iterator iterator = sFileTypeMap.keySet().iterator(); 146 | 147 | while (iterator.hasNext()) { 148 | if (builder.length() > 0) 149 | builder.append(','); 150 | builder.append(iterator.next()); 151 | } 152 | sFileExtensions = builder.toString(); 153 | } 154 | 155 | public static boolean isAudioFileType(int fileType) { 156 | return (fileType >= FIRST_AUDIO_FILE_TYPE && fileType <= LAST_AUDIO_FILE_TYPE); 157 | } 158 | 159 | public static boolean isVideoFileType(int fileType) { 160 | return (fileType >= FIRST_VIDEO_FILE_TYPE && fileType <= LAST_VIDEO_FILE_TYPE); 161 | } 162 | 163 | public static MediaFileType getFileType(String path) { 164 | int lastDot = path.lastIndexOf("."); 165 | if (lastDot < 0) 166 | return null; 167 | return sFileTypeMap.get(path.substring(lastDot + 1).toUpperCase()); 168 | } 169 | 170 | public static int getFileTypeForMimeType(String mimeType) { 171 | Integer value = sMimeTypeMap.get(mimeType); 172 | return (value == null ? 0 : value.intValue()); 173 | } 174 | 175 | } -------------------------------------------------------------------------------- /android/src/main/java/io/vov/vitamio/MediaFormat.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 The Android Open Source Project 3 | * Copyright (C) 2013 YIXIA.COM 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package io.vov.vitamio; 18 | import java.nio.ByteBuffer; 19 | import java.util.HashMap; 20 | import java.util.Map; 21 | 22 | /** 23 | * Encapsulates the information describing the format of media data, 24 | * be it audio or video. 25 | * 26 | * The format of the media data is specified as string/value pairs. 27 | * 28 | * Keys common to all audio/video formats, all keys not marked optional are mandatory: 29 | * 30 | * 31 | * 32 | * 33 | * 34 | * 35 | *
NameValue TypeDescription
{@link #KEY_MIME}StringThe type of the format.
{@link #KEY_MAX_INPUT_SIZE}Integeroptional, maximum size of a buffer of input data
{@link #KEY_BIT_RATE}Integerencoder-only, desired bitrate in bits/second
36 | * 37 | * Video formats have the following keys: 38 | * 39 | * 40 | * 41 | * 42 | * 44 | * 45 | * 46 | * 47 | * 48 | * 49 | * 50 | *
NameValue TypeDescription
{@link #KEY_WIDTH}Integer
{@link #KEY_HEIGHT}Integer
{@link #KEY_COLOR_FORMAT}Integerset by the user 43 | * for encoders, readable in the output format of decoders
{@link #KEY_FRAME_RATE}Integer or Floatencoder-only
{@link #KEY_I_FRAME_INTERVAL}Integerencoder-only
{@link #KEY_MAX_WIDTH}Integerdecoder-only, optional, max-resolution width
{@link #KEY_MAX_HEIGHT}Integerdecoder-only, optional, max-resolution height
{@link #KEY_REPEAT_PREVIOUS_FRAME_AFTER}Longvideo encoder in surface-mode only
{@link #KEY_PUSH_BLANK_BUFFERS_ON_STOP}Integer(1)video decoder rendering to a surface only
51 | * Specify both {@link #KEY_MAX_WIDTH} and {@link #KEY_MAX_HEIGHT} to enable 52 | * adaptive playback (seamless resolution change) for a video decoder that 53 | * supports it ({@link MediaCodecInfo.CodecCapabilities#FEATURE_AdaptivePlayback}). 54 | * The values are used as hints for the codec: they are the maximum expected 55 | * resolution to prepare for. Depending on codec support, preparing for larger 56 | * maximum resolution may require more memory even if that resolution is never 57 | * reached. These fields have no effect for codecs that do not support adaptive 58 | * playback.

59 | * 60 | * Audio formats have the following keys: 61 | * 62 | * 63 | * 64 | * 65 | * 66 | * 67 | * 68 | * 69 | *
NameValue TypeDescription
{@link #KEY_CHANNEL_COUNT}Integer
{@link #KEY_SAMPLE_RATE}Integer
{@link #KEY_IS_ADTS}Integeroptional, if decoding AAC audio content, setting this key to 1 indicates that each audio frame is prefixed by the ADTS header.
{@link #KEY_AAC_PROFILE}Integerencoder-only, optional, if content is AAC audio, specifies the desired profile.
{@link #KEY_CHANNEL_MASK}Integeroptional, a mask of audio channel assignments
{@link #KEY_FLAC_COMPRESSION_LEVEL}Integerencoder-only, optional, if content is FLAC audio, specifies the desired compression level.
70 | * 71 | * Subtitle formats have the following keys: 72 | * 73 | * 74 | * 75 | *
{@link #KEY_MIME}StringThe type of the format.
{@link #KEY_LANGUAGE}StringThe language of the content.
76 | */ 77 | public final class MediaFormat { 78 | private Map mMap; 79 | 80 | /** 81 | * A key describing the mime type of the MediaFormat. 82 | * The associated value is a string. 83 | */ 84 | public static final String KEY_MIME = "mime"; 85 | 86 | /** 87 | * A key describing the language of the content, using either ISO 639-1 88 | * or 639-2/T codes. The associated value is a string. 89 | */ 90 | public static final String KEY_LANGUAGE = "language"; 91 | 92 | /** 93 | * A key describing the title of the content 94 | */ 95 | public static final String KEY_TITLE = "title"; 96 | 97 | /** 98 | * A key describing the external subtitle path 99 | */ 100 | public static final String KEY_PATH = "path"; 101 | 102 | /** 103 | * A key describing the sample rate of an audio format. 104 | * The associated value is an integer 105 | */ 106 | public static final String KEY_SAMPLE_RATE = "sample-rate"; 107 | 108 | /** 109 | * A key describing the number of channels in an audio format. 110 | * The associated value is an integer 111 | */ 112 | public static final String KEY_CHANNEL_COUNT = "channel-count"; 113 | 114 | /** 115 | * A key describing the width of the content in a video format. 116 | * The associated value is an integer 117 | */ 118 | public static final String KEY_WIDTH = "width"; 119 | 120 | /** 121 | * A key describing the height of the content in a video format. 122 | * The associated value is an integer 123 | */ 124 | public static final String KEY_HEIGHT = "height"; 125 | 126 | /** 127 | * A key describing the maximum expected width of the content in a video 128 | * decoder format, in case there are resolution changes in the video content. 129 | * The associated value is an integer 130 | */ 131 | public static final String KEY_MAX_WIDTH = "max-width"; 132 | 133 | /** 134 | * A key describing the maximum expected height of the content in a video 135 | * decoder format, in case there are resolution changes in the video content. 136 | * The associated value is an integer 137 | */ 138 | public static final String KEY_MAX_HEIGHT = "max-height"; 139 | 140 | /** A key describing the maximum size in bytes of a buffer of data 141 | * described by this MediaFormat. 142 | * The associated value is an integer 143 | */ 144 | public static final String KEY_MAX_INPUT_SIZE = "max-input-size"; 145 | 146 | /** 147 | * A key describing the bitrate in bits/sec. 148 | * The associated value is an integer 149 | */ 150 | public static final String KEY_BIT_RATE = "bitrate"; 151 | 152 | /** 153 | * A key describing the color format of the content in a video format. 154 | * Constants are declared in {@link android.media.MediaCodecInfo.CodecCapabilities}. 155 | */ 156 | public static final String KEY_COLOR_FORMAT = "color-format"; 157 | 158 | /** 159 | * A key describing the frame rate of a video format in frames/sec. 160 | * The associated value is an integer or a float. 161 | */ 162 | public static final String KEY_FRAME_RATE = "frame-rate"; 163 | 164 | /** 165 | * A key describing the frequency of I frames expressed in secs 166 | * between I frames. 167 | * The associated value is an integer. 168 | */ 169 | public static final String KEY_I_FRAME_INTERVAL = "i-frame-interval"; 170 | 171 | /** 172 | * @hide 173 | */ 174 | public static final String KEY_STRIDE = "stride"; 175 | /** 176 | * @hide 177 | */ 178 | public static final String KEY_SLICE_HEIGHT = "slice-height"; 179 | 180 | /** 181 | * Applies only when configuring a video encoder in "surface-input" mode. 182 | * The associated value is a long and gives the time in microseconds 183 | * after which the frame previously submitted to the encoder will be 184 | * repeated (once) if no new frame became available since. 185 | */ 186 | public static final String KEY_REPEAT_PREVIOUS_FRAME_AFTER 187 | = "repeat-previous-frame-after"; 188 | 189 | /** 190 | * If specified when configuring a video decoder rendering to a surface, 191 | * causes the decoder to output "blank", i.e. black frames to the surface 192 | * when stopped to clear out any previously displayed contents. 193 | * The associated value is an integer of value 1. 194 | */ 195 | public static final String KEY_PUSH_BLANK_BUFFERS_ON_STOP 196 | = "push-blank-buffers-on-shutdown"; 197 | 198 | /** 199 | * A key describing the duration (in microseconds) of the content. 200 | * The associated value is a long. 201 | */ 202 | public static final String KEY_DURATION = "durationUs"; 203 | 204 | /** 205 | * A key mapping to a value of 1 if the content is AAC audio and 206 | * audio frames are prefixed with an ADTS header. 207 | * The associated value is an integer (0 or 1). 208 | * This key is only supported when _decoding_ content, it cannot 209 | * be used to configure an encoder to emit ADTS output. 210 | */ 211 | public static final String KEY_IS_ADTS = "is-adts"; 212 | 213 | /** 214 | * A key describing the channel composition of audio content. This mask 215 | * is composed of bits drawn from channel mask definitions in {@link android.media.AudioFormat}. 216 | * The associated value is an integer. 217 | */ 218 | public static final String KEY_CHANNEL_MASK = "channel-mask"; 219 | 220 | /** 221 | * A key describing the AAC profile to be used (AAC audio formats only). 222 | * Constants are declared in {@link android.media.MediaCodecInfo.CodecProfileLevel}. 223 | */ 224 | public static final String KEY_AAC_PROFILE = "aac-profile"; 225 | 226 | /** 227 | * A key describing the FLAC compression level to be used (FLAC audio format only). 228 | * The associated value is an integer ranging from 0 (fastest, least compression) 229 | * to 8 (slowest, most compression). 230 | */ 231 | public static final String KEY_FLAC_COMPRESSION_LEVEL = "flac-compression-level"; 232 | 233 | /** 234 | * A key for boolean AUTOSELECT behavior for the track. Tracks with AUTOSELECT=true 235 | * are considered when automatically selecting a track without specific user 236 | * choice, based on the current locale. 237 | * This is currently only used for subtitle tracks, when the user selected 238 | * 'Default' for the captioning locale. 239 | * The associated value is an integer, where non-0 means TRUE. This is an optional 240 | * field; if not specified, AUTOSELECT defaults to TRUE. 241 | */ 242 | public static final String KEY_IS_AUTOSELECT = "is-autoselect"; 243 | 244 | /** 245 | * A key for boolean DEFAULT behavior for the track. The track with DEFAULT=true is 246 | * selected in the absence of a specific user choice. 247 | * This is currently only used for subtitle tracks, when the user selected 248 | * 'Default' for the captioning locale. 249 | * The associated value is an integer, where non-0 means TRUE. This is an optional 250 | * field; if not specified, DEFAULT is considered to be FALSE. 251 | */ 252 | public static final String KEY_IS_DEFAULT = "is-default"; 253 | 254 | 255 | /** 256 | * A key for the FORCED field for subtitle tracks. True if it is a 257 | * forced subtitle track. Forced subtitle tracks are essential for the 258 | * content and are shown even when the user turns off Captions. They 259 | * are used for example to translate foreign/alien dialogs or signs. 260 | * The associated value is an integer, where non-0 means TRUE. This is an 261 | * optional field; if not specified, FORCED defaults to FALSE. 262 | */ 263 | public static final String KEY_IS_FORCED_SUBTITLE = "is-forced-subtitle"; 264 | 265 | /* package private */ MediaFormat(Map map) { 266 | mMap = map; 267 | } 268 | 269 | /** 270 | * Creates an empty MediaFormat 271 | */ 272 | public MediaFormat() { 273 | mMap = new HashMap(); 274 | } 275 | 276 | /* package private */ Map getMap() { 277 | return mMap; 278 | } 279 | 280 | /** 281 | * Returns true iff a key of the given name exists in the format. 282 | */ 283 | public final boolean containsKey(String name) { 284 | return mMap.containsKey(name); 285 | } 286 | 287 | /** 288 | * Returns the value of an integer key. 289 | */ 290 | public final int getInteger(String name) { 291 | return ((Integer)mMap.get(name)).intValue(); 292 | } 293 | 294 | /** 295 | * Returns the value of an integer key, or the default value if the 296 | * key is missing or is for another type value. 297 | * @hide 298 | */ 299 | public final int getInteger(String name, int defaultValue) { 300 | try { 301 | return getInteger(name); 302 | } 303 | catch (NullPointerException e) { /* no such field */ } 304 | catch (ClassCastException e) { /* field of different type */ } 305 | return defaultValue; 306 | } 307 | 308 | /** 309 | * Returns the value of a long key. 310 | */ 311 | public final long getLong(String name) { 312 | return ((Long)mMap.get(name)).longValue(); 313 | } 314 | 315 | /** 316 | * Returns the value of a float key. 317 | */ 318 | public final float getFloat(String name) { 319 | return ((Float)mMap.get(name)).floatValue(); 320 | } 321 | 322 | /** 323 | * Returns the value of a string key. 324 | */ 325 | public final String getString(String name) { 326 | return (String)mMap.get(name); 327 | } 328 | 329 | /** 330 | * Returns the value of a ByteBuffer key. 331 | */ 332 | public final ByteBuffer getByteBuffer(String name) { 333 | return (ByteBuffer)mMap.get(name); 334 | } 335 | 336 | /** 337 | * Sets the value of an integer key. 338 | */ 339 | public final void setInteger(String name, int value) { 340 | mMap.put(name, Integer.valueOf(value)); 341 | } 342 | 343 | /** 344 | * Sets the value of a long key. 345 | */ 346 | public final void setLong(String name, long value) { 347 | mMap.put(name, Long.valueOf(value)); 348 | } 349 | 350 | /** 351 | * Sets the value of a float key. 352 | */ 353 | public final void setFloat(String name, float value) { 354 | mMap.put(name, Float.valueOf(value)); 355 | } 356 | 357 | /** 358 | * Sets the value of a string key. 359 | */ 360 | public final void setString(String name, String value) { 361 | mMap.put(name, value); 362 | } 363 | 364 | /** 365 | * Sets the value of a ByteBuffer key. 366 | */ 367 | public final void setByteBuffer(String name, ByteBuffer bytes) { 368 | mMap.put(name, bytes); 369 | } 370 | 371 | /** 372 | * Creates a minimal audio format. 373 | * @param mime The mime type of the content. 374 | * @param sampleRate The sampling rate of the content. 375 | * @param channelCount The number of audio channels in the content. 376 | */ 377 | public static final MediaFormat createAudioFormat( 378 | String mime, 379 | int sampleRate, 380 | int channelCount) { 381 | MediaFormat format = new MediaFormat(); 382 | format.setString(KEY_MIME, mime); 383 | format.setInteger(KEY_SAMPLE_RATE, sampleRate); 384 | format.setInteger(KEY_CHANNEL_COUNT, channelCount); 385 | 386 | return format; 387 | } 388 | 389 | /** 390 | * Creates a minimal subtitle format. 391 | * @param title The content of the Subtitle 392 | * @param language The language of the content, using either ISO 639-1 or 639-2/T 393 | * codes. Specify null or "und" if language information is only included 394 | * in the content. (This will also work if there are multiple language 395 | * tracks in the content.) 396 | */ 397 | public static final MediaFormat createSubtitleFormat( 398 | String title, 399 | String language) { 400 | MediaFormat format = new MediaFormat(); 401 | format.setString(KEY_TITLE, title); 402 | format.setString(KEY_LANGUAGE, language); 403 | 404 | return format; 405 | } 406 | 407 | /** 408 | * Creates a minimal video format. 409 | * @param mime The mime type of the content. 410 | * @param width The width of the content (in pixels) 411 | * @param height The height of the content (in pixels) 412 | */ 413 | public static final MediaFormat createVideoFormat( 414 | String mime, 415 | int width, 416 | int height) { 417 | MediaFormat format = new MediaFormat(); 418 | format.setString(KEY_MIME, mime); 419 | format.setInteger(KEY_WIDTH, width); 420 | format.setInteger(KEY_HEIGHT, height); 421 | 422 | return format; 423 | } 424 | 425 | @Override 426 | public String toString() { 427 | return mMap.toString(); 428 | } 429 | } 430 | -------------------------------------------------------------------------------- /android/src/main/java/io/vov/vitamio/MediaMetadataRetriever.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2008 The Android Open Source Project 3 | * Copyright (C) 2013 YIXIA.COM 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package io.vov.vitamio; 19 | 20 | import android.content.ContentResolver; 21 | import android.content.Context; 22 | import android.content.res.AssetFileDescriptor; 23 | import android.graphics.Bitmap; 24 | import android.net.Uri; 25 | import android.util.Log; 26 | 27 | import io.vov.vitamio.utils.FileUtils; 28 | 29 | import java.io.FileDescriptor; 30 | import java.io.IOException; 31 | 32 | /** 33 | * MediaMetadataRetriever is used to get meta data from any media file 34 | *

35 | * 36 | *

 37 |  * MediaMetadataRetriever mmr = new MediaMetadataRetriever(this);
 38 |  * mmr.setDataSource(this, mediaUri);
 39 |  * String title = mmr.extractMetadata(METADATA_KEY_TITLE);
 40 |  * Bitmap frame = mmr.getFrameAtTime(-1);
 41 |  * mmr.release();
 42 |  * 
43 | */ 44 | public class MediaMetadataRetriever { 45 | private AssetFileDescriptor mFD = null; 46 | 47 | static { 48 | String LIB_ROOT = Vitamio.getLibraryPath(); 49 | Log.i("LIB ROOT: %s", LIB_ROOT); 50 | System.load( LIB_ROOT + "libstlport_shared.so"); 51 | System.load( LIB_ROOT +"libvscanner.so"); 52 | loadFFmpeg_native( LIB_ROOT + "libffmpeg.so"); 53 | native_init(); 54 | } 55 | 56 | private int mNativeContext; 57 | 58 | public MediaMetadataRetriever(Context ctx) { 59 | native_setup(); 60 | } 61 | 62 | private static native boolean loadFFmpeg_native(String ffmpegPath); 63 | 64 | public void setDataSource(Context context, Uri uri) throws IOException, IllegalArgumentException, 65 | SecurityException, IllegalStateException { 66 | if (context == null || uri == null) 67 | throw new IllegalArgumentException(); 68 | String scheme = uri.getScheme(); 69 | if (scheme == null || scheme.equals("file")) { 70 | setDataSource(FileUtils.getPath(uri.toString())); 71 | return; 72 | } 73 | 74 | try { 75 | ContentResolver resolver = context.getContentResolver(); 76 | mFD = resolver.openAssetFileDescriptor(uri, "r"); 77 | if (mFD == null) 78 | return; 79 | setDataSource(mFD.getParcelFileDescriptor().getFileDescriptor()); 80 | return; 81 | } catch (Exception e) { 82 | closeFD(); 83 | } 84 | Log.e("Couldn't open file on client side, trying server side %s", uri.toString()); 85 | setDataSource(uri.toString()); 86 | return; 87 | } 88 | 89 | public native void setDataSource(String path) throws IOException, IllegalArgumentException, 90 | IllegalStateException; 91 | 92 | public native void setDataSource(FileDescriptor fd) throws IOException, IllegalArgumentException, 93 | IllegalStateException; 94 | 95 | /** 96 | * Call this method after setDataSource(). This method retrieves the meta data 97 | * value associated with the keyCode. 98 | * 99 | * The keyCode currently supported is listed below as METADATA_XXX constants. 100 | * With any other value, it returns a null pointer. 101 | * 102 | * @param keyCode One of the constants listed below at the end of the class. 103 | * @return The meta data value associate with the given keyCode on success; 104 | * null on failure. 105 | */ 106 | public native String extractMetadata(String keyCode) throws IllegalStateException; 107 | 108 | public native Bitmap getFrameAtTime(long timeUs) throws IllegalStateException; 109 | 110 | /** 111 | * Call this method after setDataSource(). This method finds the optional 112 | * graphic or album/cover art associated associated with the data source. If 113 | * there are more than one pictures, (any) one of them is returned. 114 | * 115 | * @return null if no such graphic is found. 116 | */ 117 | public native byte[] getEmbeddedPicture() throws IllegalStateException; 118 | 119 | private native void _release(); 120 | 121 | private native void native_setup(); 122 | 123 | private static native final void native_init(); 124 | 125 | private native final void native_finalize(); 126 | 127 | public void release() { 128 | _release(); 129 | closeFD(); 130 | } 131 | 132 | @Override 133 | protected void finalize() throws Throwable { 134 | try { 135 | native_finalize(); 136 | } finally { 137 | super.finalize(); 138 | } 139 | } 140 | 141 | private void closeFD() { 142 | if (mFD != null) { 143 | try { 144 | mFD.close(); 145 | } catch (IOException e) { 146 | } 147 | mFD = null; 148 | } 149 | } 150 | 151 | /* 152 | * Do not change these metadata key values without updating their 153 | * counterparts in c file 154 | */ 155 | 156 | /** 157 | * The metadata key to retrieve the information about the album title of the 158 | * data source. 159 | */ 160 | public static final String METADATA_KEY_ALBUM = "album"; 161 | /** 162 | * The metadata key to retrieve the main creator of the set/album, if 163 | * different from artist. e.g. "Various Artists" for compilation albums. 164 | */ 165 | public static final String METADATA_KEY_ALBUM_ARTIST = "album_artist"; 166 | /** 167 | * The metadata key to retrieve the information about the artist of the data 168 | * source. 169 | */ 170 | public static final String METADATA_KEY_ARTIST = "artist"; 171 | 172 | /** 173 | * The metadata key to retrieve the any additional description of the file. 174 | */ 175 | public static final String METADATA_KEY_COMMENT = "comment"; 176 | /** 177 | * The metadata key to retrieve the information about the author of the data 178 | * source. 179 | */ 180 | public static final String METADATA_KEY_AUTHOR = "author"; 181 | /** 182 | * The metadata key to retrieve the information about the composer of the data 183 | * source. 184 | */ 185 | public static final String METADATA_KEY_COMPOSER = "composer"; 186 | /** 187 | * The metadata key to retrieve the name of copyright holder. 188 | */ 189 | public static final String METADATA_KEY_COPYRIGHT = "copyright"; 190 | /** 191 | * The metadata key to retrieve the date when the file was created, preferably 192 | * in ISO 8601. 193 | */ 194 | public static final String METADATA_KEY_CREATION_TIME = "creation_time"; 195 | /** 196 | * The metadata key to retrieve the date when the work was created, preferably 197 | * in ISO 8601. 198 | */ 199 | public static final String METADATA_KEY_DATE = "date"; 200 | /** 201 | * The metadata key to retrieve the number of a subset, e.g. disc in a 202 | * multi-disc collection. 203 | */ 204 | public static final String METADATA_KEY_DISC = "disc"; 205 | /** 206 | * The metadata key to retrieve the name/settings of the software/hardware 207 | * that produced the file. 208 | */ 209 | public static final String METADATA_KEY_ENCODER = "encoder"; 210 | /** 211 | * The metadata key to retrieve the person/group who created the file. 212 | */ 213 | public static final String METADATA_KEY_ENCODED_BY = "encoded_by"; 214 | /** 215 | * The metadata key to retrieve the original name of the file. 216 | */ 217 | public static final String METADATA_KEY_FILENAME = "filename"; 218 | /** 219 | * The metadata key to retrieve the content type or genre of the data source. 220 | */ 221 | public static final String METADATA_KEY_GENRE = "genre"; 222 | /** 223 | * The metadata key to retrieve the main language in which the work is 224 | * performed, preferably in ISO 639-2 format. Multiple languages can be 225 | * specified by separating them with commas. 226 | */ 227 | public static final String METADATA_KEY_LANGUAGE = "language"; 228 | /** 229 | * The metadata key to retrieve the artist who performed the work, if 230 | * different from artist. E.g for "Also sprach Zarathustra", artist would be 231 | * "Richard Strauss" and performer "London Philharmonic Orchestra". 232 | */ 233 | public static final String METADATA_KEY_PERFORMER = "performer"; 234 | /** 235 | * The metadata key to retrieve the name of the label/publisher. 236 | */ 237 | public static final String METADATA_KEY_PUBLISHER = "publisher"; 238 | /** 239 | * The metadata key to retrieve the name of the service in broadcasting 240 | * (channel name). 241 | */ 242 | public static final String METADATA_KEY_SERVICE_NAME = "service_name"; 243 | /** 244 | * The metadata key to retrieve the name of the service provider in 245 | * broadcasting. 246 | */ 247 | public static final String METADATA_KEY_SERVICE_PROVIDER = "service_provider"; 248 | /** 249 | * The metadata key to retrieve the data source title. 250 | */ 251 | public static final String METADATA_KEY_TITLE = "title"; 252 | /** 253 | * The metadata key to retrieve the number of this work in the set, can be in 254 | * form current/total. 255 | */ 256 | public static final String METADATA_KEY_TRACK = "track"; 257 | /** 258 | * The metadata key to retrieve the total bitrate of the bitrate variant that 259 | * the current stream is part of. 260 | */ 261 | public static final String METADATA_KEY_VARIANT_BITRATE = "bitrate"; 262 | /** 263 | * The metadata key to retrieve the playback duration of the data source. 264 | */ 265 | public static final String METADATA_KEY_DURATION = "duration"; 266 | /** 267 | * The metadata key to retrieve the audio codec of the work. 268 | */ 269 | public static final String METADATA_KEY_AUDIO_CODEC = "audio_codec"; 270 | /** 271 | * The metadata key to retrieve the video codec of the work. 272 | */ 273 | public static final String METADATA_KEY_VIDEO_CODEC = "video_codec"; 274 | /** 275 | * This key retrieves the video rotation angle in degrees, if available. The 276 | * video rotation angle may be 0, 90, 180, or 270 degrees. 277 | */ 278 | public static final String METADATA_KEY_VIDEO_ROTATION = "rotate"; 279 | /** 280 | * If the media contains video, this key retrieves its width. 281 | */ 282 | public static final String METADATA_KEY_VIDEO_WIDTH = "width"; 283 | /** 284 | * If the media contains video, this key retrieves its height. 285 | */ 286 | public static final String METADATA_KEY_VIDEO_HEIGHT = "height"; 287 | /** 288 | * The metadata key to retrieve the number of tracks, such as audio, video, 289 | * text, in the data source, such as a mp4 or 3gpp file. 290 | */ 291 | public static final String METADATA_KEY_NUM_TRACKS = "num_tracks"; 292 | /** 293 | * If this key exists the media contains audio content. if has audio, return 294 | * 1. 295 | */ 296 | public static final String METADATA_KEY_HAS_AUDIO = "has_audio"; 297 | /** 298 | * If this key exists the media contains video content. if has video, return 299 | * 1. 300 | */ 301 | public static final String METADATA_KEY_HAS_VIDEO = "has_video"; 302 | 303 | } -------------------------------------------------------------------------------- /android/src/main/java/io/vov/vitamio/MediaScanner.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2006 The Android Open Source Project 3 | * Copyright (C) 2013 YIXIA.COM 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package io.vov.vitamio; 19 | 20 | import android.content.ContentProviderClient; 21 | import android.content.ContentUris; 22 | import android.content.ContentValues; 23 | import android.content.Context; 24 | import android.database.Cursor; 25 | import android.database.SQLException; 26 | import android.net.Uri; 27 | import android.os.RemoteException; 28 | import android.text.TextUtils; 29 | 30 | import io.vov.vitamio.provider.MediaStore; 31 | import io.vov.vitamio.provider.MediaStore.Video; 32 | import io.vov.vitamio.utils.ContextUtils; 33 | import io.vov.vitamio.utils.FileUtils; 34 | import io.vov.vitamio.utils.Log; 35 | 36 | import java.io.File; 37 | import java.util.HashMap; 38 | import java.util.Iterator; 39 | 40 | public class MediaScanner { 41 | private static final String[] VIDEO_PROJECTION = new String[]{Video.Media._ID, Video.Media.DATA, Video.Media.DATE_MODIFIED,}; 42 | private static final int ID_VIDEO_COLUMN_INDEX = 0; 43 | private static final int PATH_VIDEO_COLUMN_INDEX = 1; 44 | private static final int DATE_MODIFIED_VIDEO_COLUMN_INDEX = 2; 45 | private Context mContext; 46 | private ContentProviderClient mProvider; 47 | private boolean mCaseInsensitivePaths; 48 | private HashMap mFileCache; 49 | private MyMediaScannerClient mClient = new MyMediaScannerClient(); 50 | 51 | public MediaScanner(Context ctx) { 52 | mContext = ctx; 53 | native_init(mClient); 54 | } 55 | 56 | private static native boolean loadFFmpeg_native(String ffmpegPath); 57 | 58 | private void initialize() { 59 | mCaseInsensitivePaths = true; 60 | } 61 | 62 | private void prescan(String filePath) throws RemoteException { 63 | mProvider = mContext.getContentResolver().acquireContentProviderClient(MediaStore.AUTHORITY); 64 | Cursor c = null; 65 | String where = null; 66 | String[] selectionArgs = null; 67 | 68 | if (mFileCache == null) 69 | mFileCache = new HashMap(); 70 | else 71 | mFileCache.clear(); 72 | 73 | try { 74 | if (filePath != null) { 75 | where = Video.Media.DATA + "=?"; 76 | selectionArgs = new String[]{filePath}; 77 | } 78 | 79 | c = mProvider.query(Video.Media.CONTENT_URI, VIDEO_PROJECTION, where, selectionArgs, null); 80 | if (c != null) { 81 | try { 82 | while (c.moveToNext()) { 83 | long rowId = c.getLong(ID_VIDEO_COLUMN_INDEX); 84 | String path = c.getString(PATH_VIDEO_COLUMN_INDEX); 85 | long lastModified = c.getLong(DATE_MODIFIED_VIDEO_COLUMN_INDEX); 86 | if (path.startsWith("/")) { 87 | File tempFile = new File(path); 88 | if (!TextUtils.isEmpty(filePath) && !tempFile.exists()) { 89 | mProvider.delete(Video.Media.CONTENT_URI, where, selectionArgs); 90 | return; 91 | } 92 | path = FileUtils.getCanonical(tempFile); 93 | String key = mCaseInsensitivePaths ? path.toLowerCase() : path; 94 | mFileCache.put(key, new FileCacheEntry(Video.Media.CONTENT_URI, rowId, path, lastModified)); 95 | } 96 | } 97 | } finally { 98 | c.close(); 99 | c = null; 100 | } 101 | } 102 | } finally { 103 | if (c != null) { 104 | c.close(); 105 | } 106 | } 107 | } 108 | 109 | ; 110 | 111 | private void postscan(String[] directories) throws RemoteException { 112 | Iterator iterator = mFileCache.values().iterator(); 113 | 114 | while (iterator.hasNext()) { 115 | FileCacheEntry entry = iterator.next(); 116 | String path = entry.mPath; 117 | 118 | if (!entry.mSeenInFileSystem) { 119 | if (inScanDirectory(path, directories) && !new File(path).exists()) { 120 | mProvider.delete(ContentUris.withAppendedId(entry.mTableUri, entry.mRowId), null, null); 121 | iterator.remove(); 122 | } 123 | } 124 | } 125 | 126 | mFileCache.clear(); 127 | mFileCache = null; 128 | mProvider.release(); 129 | mProvider = null; 130 | } 131 | 132 | private boolean inScanDirectory(String path, String[] directories) { 133 | for (int i = 0; i < directories.length; i++) { 134 | if (path.startsWith(directories[i])) 135 | return true; 136 | } 137 | return false; 138 | } 139 | 140 | public void scanDirectories(String[] directories) { 141 | try { 142 | long start = System.currentTimeMillis(); 143 | prescan(null); 144 | long prescan = System.currentTimeMillis(); 145 | 146 | for (int i = 0; i < directories.length; i++) { 147 | if (!TextUtils.isEmpty(directories[i])) { 148 | directories[i] = ContextUtils.fixLastSlash(directories[i]); 149 | processDirectory(directories[i], MediaFile.sFileExtensions); 150 | } 151 | } 152 | 153 | long scan = System.currentTimeMillis(); 154 | postscan(directories); 155 | long end = System.currentTimeMillis(); 156 | 157 | Log.d(" prescan time: %dms", prescan - start); 158 | Log.d(" scan time: %dms", scan - prescan); 159 | Log.d("postscan time: %dms", end - scan); 160 | Log.d(" total time: %dms", end - start); 161 | } catch (SQLException e) { 162 | Log.e("SQLException in MediaScanner.scan()", e); 163 | } catch (UnsupportedOperationException e) { 164 | Log.e("UnsupportedOperationException in MediaScanner.scan()", e); 165 | } catch (RemoteException e) { 166 | Log.e("RemoteException in MediaScanner.scan()", e); 167 | } 168 | } 169 | 170 | public Uri scanSingleFile(String path, String mimeType) { 171 | try { 172 | prescan(path); 173 | File file = new File(path); 174 | long lastModifiedSeconds = file.lastModified() / 1000; 175 | 176 | return mClient.doScanFile(path, lastModifiedSeconds, file.length(), true); 177 | } catch (RemoteException e) { 178 | Log.e("RemoteException in MediaScanner.scanFile()", e); 179 | return null; 180 | } 181 | } 182 | 183 | static { 184 | String LIB_ROOT = Vitamio.getLibraryPath(); 185 | Log.i("LIB ROOT: %s", LIB_ROOT); 186 | System.load( LIB_ROOT + "libstlport_shared.so"); 187 | System.load( LIB_ROOT + "libvscanner.so"); 188 | loadFFmpeg_native( LIB_ROOT + "libffmpeg.so"); 189 | } 190 | 191 | private native void processDirectory(String path, String extensions); 192 | 193 | private native boolean processFile(String path, String mimeType); 194 | 195 | private native final void native_init(MediaScannerClient client); 196 | 197 | public native void release(); 198 | 199 | private native final void native_finalize(); 200 | 201 | @Override 202 | protected void finalize() throws Throwable { 203 | try { 204 | native_finalize(); 205 | } finally { 206 | super.finalize(); 207 | } 208 | } 209 | 210 | private static class FileCacheEntry { 211 | Uri mTableUri; 212 | long mRowId; 213 | String mPath; 214 | long mLastModified; 215 | boolean mLastModifiedChanged; 216 | boolean mSeenInFileSystem; 217 | 218 | FileCacheEntry(Uri tableUri, long rowId, String path, long lastModified) { 219 | mTableUri = tableUri; 220 | mRowId = rowId; 221 | mPath = path; 222 | mLastModified = lastModified; 223 | mSeenInFileSystem = false; 224 | mLastModifiedChanged = false; 225 | } 226 | 227 | @Override 228 | public String toString() { 229 | return mPath; 230 | } 231 | } 232 | 233 | private class MyMediaScannerClient implements MediaScannerClient { 234 | private String mMimeType; 235 | private int mFileType; 236 | private String mPath; 237 | private long mLastModified; 238 | private long mFileSize; 239 | private String mTitle; 240 | private String mArtist; 241 | private String mAlbum; 242 | private String mLanguage; 243 | private long mDuration; 244 | private int mWidth; 245 | private int mHeight; 246 | 247 | public FileCacheEntry beginFile(String path, long lastModified, long fileSize) { 248 | int lastSlash = path.lastIndexOf('/'); 249 | if (lastSlash >= 0 && lastSlash + 2 < path.length()) { 250 | if (path.regionMatches(lastSlash + 1, "._", 0, 2)) 251 | return null; 252 | 253 | if (path.regionMatches(true, path.length() - 4, ".jpg", 0, 4)) { 254 | if (path.regionMatches(true, lastSlash + 1, "AlbumArt_{", 0, 10) || path.regionMatches(true, lastSlash + 1, "AlbumArt.", 0, 9)) { 255 | return null; 256 | } 257 | int length = path.length() - lastSlash - 1; 258 | if ((length == 17 && path.regionMatches(true, lastSlash + 1, "AlbumArtSmall", 0, 13)) || (length == 10 && path.regionMatches(true, lastSlash + 1, "Folder", 0, 6))) { 259 | return null; 260 | } 261 | } 262 | } 263 | 264 | MediaFile.MediaFileType mediaFileType = MediaFile.getFileType(path); 265 | if (mediaFileType != null) { 266 | mFileType = mediaFileType.fileType; 267 | mMimeType = mediaFileType.mimeType; 268 | } 269 | 270 | String key = FileUtils.getCanonical(new File(path)); 271 | if (mCaseInsensitivePaths) 272 | key = path.toLowerCase(); 273 | FileCacheEntry entry = mFileCache.get(key); 274 | if (entry == null) { 275 | entry = new FileCacheEntry(null, 0, path, 0); 276 | mFileCache.put(key, entry); 277 | } 278 | entry.mSeenInFileSystem = true; 279 | 280 | long delta = lastModified - entry.mLastModified; 281 | if (delta > 1 || delta < -1) { 282 | entry.mLastModified = lastModified; 283 | entry.mLastModifiedChanged = true; 284 | } 285 | 286 | mPath = path; 287 | mLastModified = lastModified; 288 | mFileSize = fileSize; 289 | mTitle = null; 290 | mDuration = 0; 291 | 292 | return entry; 293 | } 294 | 295 | public void scanFile(String path, long lastModified, long fileSize) { 296 | Log.i("scanFile: %s", path); 297 | doScanFile(path, lastModified, fileSize, false); 298 | } 299 | 300 | public Uri doScanFile(String path, long lastModified, long fileSize, boolean scanAlways) { 301 | Uri result = null; 302 | try { 303 | FileCacheEntry entry = beginFile(path, lastModified, fileSize); 304 | if (entry != null && (entry.mLastModifiedChanged || scanAlways)) { 305 | if (processFile(path, null)) { 306 | result = endFile(entry); 307 | } else { 308 | if (mCaseInsensitivePaths) 309 | mFileCache.remove(path.toLowerCase()); 310 | else 311 | mFileCache.remove(path); 312 | } 313 | } 314 | } catch (RemoteException e) { 315 | Log.e("RemoteException in MediaScanner.scanFile()", e); 316 | } 317 | return result; 318 | } 319 | 320 | private int parseSubstring(String s, int start, int defaultValue) { 321 | int length = s.length(); 322 | if (start == length) 323 | return defaultValue; 324 | 325 | char ch = s.charAt(start++); 326 | if (ch < '0' || ch > '9') 327 | return defaultValue; 328 | 329 | int result = ch - '0'; 330 | while (start < length) { 331 | ch = s.charAt(start++); 332 | if (ch < '0' || ch > '9') 333 | return result; 334 | result = result * 10 + (ch - '0'); 335 | } 336 | 337 | return result; 338 | } 339 | 340 | public void handleStringTag(String name, byte[] valueBytes, String valueEncoding) { 341 | String value; 342 | try { 343 | value = new String(valueBytes, valueEncoding); 344 | } catch (Exception e) { 345 | Log.e("handleStringTag", e); 346 | value = new String(valueBytes); 347 | } 348 | Log.i("%s : %s", name, value); 349 | 350 | if (name.equalsIgnoreCase("title")) { 351 | mTitle = value; 352 | } else if (name.equalsIgnoreCase("artist")) { 353 | mArtist = value.trim(); 354 | } else if (name.equalsIgnoreCase("albumartist")) { 355 | if (TextUtils.isEmpty(mArtist)) 356 | mArtist = value.trim(); 357 | } else if (name.equalsIgnoreCase("album")) { 358 | mAlbum = value.trim(); 359 | } else if (name.equalsIgnoreCase("language")) { 360 | mLanguage = value.trim(); 361 | } else if (name.equalsIgnoreCase("duration")) { 362 | mDuration = parseSubstring(value, 0, 0); 363 | } else if (name.equalsIgnoreCase("width")) { 364 | mWidth = parseSubstring(value, 0, 0); 365 | } else if (name.equalsIgnoreCase("height")) { 366 | mHeight = parseSubstring(value, 0, 0); 367 | } 368 | } 369 | 370 | public void setMimeType(String mimeType) { 371 | Log.i("setMimeType: %s", mimeType); 372 | mMimeType = mimeType; 373 | mFileType = MediaFile.getFileTypeForMimeType(mimeType); 374 | } 375 | 376 | private ContentValues toValues() { 377 | ContentValues map = new ContentValues(); 378 | 379 | map.put(MediaStore.MediaColumns.DATA, mPath); 380 | map.put(MediaStore.MediaColumns.DATE_MODIFIED, mLastModified); 381 | map.put(MediaStore.MediaColumns.SIZE, mFileSize); 382 | map.put(MediaStore.MediaColumns.MIME_TYPE, mMimeType); 383 | map.put(MediaStore.MediaColumns.TITLE, mTitle); 384 | 385 | if (MediaFile.isVideoFileType(mFileType)) { 386 | map.put(Video.Media.DURATION, mDuration); 387 | map.put(Video.Media.LANGUAGE, mLanguage); 388 | map.put(Video.Media.ALBUM, mAlbum); 389 | map.put(Video.Media.ARTIST, mArtist); 390 | map.put(Video.Media.WIDTH, mWidth); 391 | map.put(Video.Media.HEIGHT, mHeight); 392 | } 393 | 394 | return map; 395 | } 396 | 397 | private Uri endFile(FileCacheEntry entry) throws RemoteException { 398 | Uri tableUri; 399 | boolean isVideo = MediaFile.isVideoFileType(mFileType) && mWidth > 0 && mHeight > 0; 400 | if (isVideo) { 401 | tableUri = Video.Media.CONTENT_URI; 402 | } else { 403 | return null; 404 | } 405 | entry.mTableUri = tableUri; 406 | 407 | ContentValues values = toValues(); 408 | String title = values.getAsString(MediaStore.MediaColumns.TITLE); 409 | if (TextUtils.isEmpty(title)) { 410 | title = values.getAsString(MediaStore.MediaColumns.DATA); 411 | int lastSlash = title.lastIndexOf('/'); 412 | if (lastSlash >= 0) { 413 | lastSlash++; 414 | if (lastSlash < title.length()) 415 | title = title.substring(lastSlash); 416 | } 417 | int lastDot = title.lastIndexOf('.'); 418 | if (lastDot > 0) 419 | title = title.substring(0, lastDot); 420 | values.put(MediaStore.MediaColumns.TITLE, title); 421 | } 422 | 423 | long rowId = entry.mRowId; 424 | 425 | Uri result = null; 426 | if (rowId == 0) { 427 | result = mProvider.insert(tableUri, values); 428 | if (result != null) { 429 | rowId = ContentUris.parseId(result); 430 | entry.mRowId = rowId; 431 | } 432 | } else { 433 | result = ContentUris.withAppendedId(tableUri, rowId); 434 | mProvider.update(result, values, null, null); 435 | } 436 | 437 | return result; 438 | } 439 | 440 | public void addNoMediaFolder(String path) { 441 | ContentValues values = new ContentValues(); 442 | values.put(MediaStore.MediaColumns.DATA, ""); 443 | String[] pathSpec = new String[]{path + '%'}; 444 | try { 445 | mProvider.update(Video.Media.CONTENT_URI, values, MediaStore.MediaColumns.DATA + " LIKE ?", pathSpec); 446 | } catch (RemoteException e) { 447 | throw new RuntimeException(); 448 | } 449 | } 450 | } 451 | } -------------------------------------------------------------------------------- /android/src/main/java/io/vov/vitamio/MediaScannerClient.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2006 The Android Open Source Project 3 | * Copyright (C) 2013 YIXIA.COM 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package io.vov.vitamio; 19 | 20 | /** 21 | * DON'T TOUCH THIS FILE IF YOU DON'T KNOW THE MediaScanner PROCEDURE!!! 22 | */ 23 | public interface MediaScannerClient { 24 | public void scanFile(String path, long lastModified, long fileSize); 25 | 26 | public void addNoMediaFolder(String path); 27 | 28 | public void handleStringTag(String name, byte[] value, String valueEncoding); 29 | 30 | public void setMimeType(String mimeType); 31 | } -------------------------------------------------------------------------------- /android/src/main/java/io/vov/vitamio/Metadata.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2006 The Android Open Source Project 3 | * Copyright (C) 2013 YIXIA.COM 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package io.vov.vitamio; 19 | 20 | import android.util.SparseArray; 21 | 22 | import java.io.UnsupportedEncodingException; 23 | import java.util.Locale; 24 | import java.util.Map; 25 | 26 | /** 27 | * See {@link io.vov.vitamio.MediaPlayer#getMetadata()} 28 | */ 29 | public class Metadata { 30 | public static final int ANY = 0; 31 | public static final int TITLE = 1; // String 32 | public static final int COMMENT = 2; // String 33 | public static final int COPYRIGHT = 3; // String 34 | public static final int ALBUM = 4; // String 35 | public static final int ARTIST = 5; // String 36 | public static final int AUTHOR = 6; // String 37 | public static final int COMPOSER = 7; // String 38 | public static final int GENRE = 8; // String 39 | public static final int DATE = 9; // Date 40 | public static final int DURATION = 10; // Integer(milliseconds) 41 | public static final int CD_TRACK_NUM = 11; // Integer (1-based) 42 | public static final int CD_TRACK_MAX = 12; // Integer 43 | public static final int RATING = 13; // String 44 | public static final int ALBUM_ART = 14; // byte[] 45 | public static final int VIDEO_FRAME = 15; // Bitmap 46 | public static final int LENGTH = 16; // Integer (bytes) 47 | public static final int BIT_RATE = 17; // Integer 48 | public static final int AUDIO_BIT_RATE = 18; // Integer 49 | public static final int VIDEO_BIT_RATE = 19; // Integer 50 | public static final int AUDIO_SAMPLE_RATE = 20; // Integer 51 | public static final int VIDEO_FRAME_RATE = 21; // Float 52 | // See RFC2046 and RFC4281. 53 | public static final int MIME_TYPE = 22; // String 54 | public static final int AUDIO_CODEC = 23; // String 55 | public static final int VIDEO_CODEC = 24; // String 56 | public static final int VIDEO_HEIGHT = 25; // Integer 57 | public static final int VIDEO_WIDTH = 26; // Integer 58 | public static final int NUM_TRACKS = 27; // Integer 59 | public static final int DRM_CRIPPLED = 28; // Boolean 60 | public static final int PAUSE_AVAILABLE = 29; // Boolean 61 | public static final int SEEK_BACKWARD_AVAILABLE = 30; // Boolean 62 | public static final int SEEK_FORWARD_AVAILABLE = 31; // Boolean 63 | public static final int SEEK_AVAILABLE = 32; // Boolean 64 | private static final int LAST_SYSTEM = 32; 65 | private static final int FIRST_CUSTOM = 8192; 66 | private SparseArray mMeta = new SparseArray(); 67 | private String mEncoding = "UTF-8"; 68 | 69 | public boolean parse(Map meta, String encoding) { 70 | String key = null; 71 | byte[] value = null; 72 | mEncoding = encoding; 73 | for (byte[] keyBytes : meta.keySet()) { 74 | try { 75 | key = new String(keyBytes, mEncoding).trim().toLowerCase(Locale.US); 76 | } catch (UnsupportedEncodingException e) { 77 | key = new String(keyBytes).trim().toLowerCase(Locale.US); 78 | } 79 | value = meta.get(keyBytes); 80 | if (key.equals("title")) { 81 | mMeta.put(TITLE, value); 82 | } else if (key.equals("comment")) { 83 | mMeta.put(COMMENT, value); 84 | } else if (key.equals("copyright")) { 85 | mMeta.put(COPYRIGHT, value); 86 | } else if (key.equals("album")) { 87 | mMeta.put(ALBUM, value); 88 | } else if (key.equals("artist")) { 89 | mMeta.put(ARTIST, value); 90 | } else if (key.equals("author")) { 91 | mMeta.put(AUTHOR, value); 92 | } else if (key.equals("composer")) { 93 | mMeta.put(COMPOSER, value); 94 | } else if (key.equals("genre")) { 95 | mMeta.put(GENRE, value); 96 | } else if (key.equals("creation_time") || key.equals("date")) { 97 | mMeta.put(DATE, value); 98 | } else if (key.equals("duration")) { 99 | mMeta.put(DURATION, value); 100 | } else if (key.equals("length")) { 101 | mMeta.put(LENGTH, value); 102 | } else if (key.equals("bit_rate")) { 103 | mMeta.put(BIT_RATE, value); 104 | } else if (key.equals("audio_bit_rate")) { 105 | mMeta.put(AUDIO_BIT_RATE, value); 106 | } else if (key.equals("video_bit_rate")) { 107 | mMeta.put(VIDEO_BIT_RATE, value); 108 | } else if (key.equals("audio_sample_rate")) { 109 | mMeta.put(AUDIO_SAMPLE_RATE, value); 110 | } else if (key.equals("video_frame_rate")) { 111 | mMeta.put(VIDEO_FRAME_RATE, value); 112 | } else if (key.equals("format")) { 113 | mMeta.put(MIME_TYPE, value); 114 | } else if (key.equals("audio_codec")) { 115 | mMeta.put(AUDIO_CODEC, value); 116 | } else if (key.equals("video_codec")) { 117 | mMeta.put(VIDEO_CODEC, value); 118 | } else if (key.equals("video_height")) { 119 | mMeta.put(VIDEO_HEIGHT, value); 120 | } else if (key.equals("video_width")) { 121 | mMeta.put(VIDEO_WIDTH, value); 122 | } else if (key.equals("num_tracks")) { 123 | mMeta.put(NUM_TRACKS, value); 124 | } else if (key.equals("cap_pause")) { 125 | mMeta.put(PAUSE_AVAILABLE, value); 126 | } else if (key.equals("cap_seek")) { 127 | mMeta.put(SEEK_AVAILABLE, value); 128 | } 129 | } 130 | 131 | return true; 132 | } 133 | 134 | public boolean has(final int metadataId) { 135 | if (!checkMetadataId(metadataId)) { 136 | throw new IllegalArgumentException("Invalid key: " + metadataId); 137 | } 138 | return mMeta.indexOfKey(metadataId) >= 0; 139 | } 140 | 141 | public String getString(final int key) { 142 | byte[] value = mMeta.get(key); 143 | if (value == null) { 144 | return null; 145 | } 146 | try { 147 | return new String(value, mEncoding); 148 | } catch (UnsupportedEncodingException e) { 149 | return new String(value); 150 | } 151 | } 152 | 153 | public int getInt(final int key) { 154 | try { 155 | return Integer.parseInt(getString(key)); 156 | } catch (Exception e) { 157 | return -1; 158 | } 159 | } 160 | 161 | public boolean getBoolean(final int key) { 162 | try { 163 | return Boolean.parseBoolean(getString(key)); 164 | } catch (Exception e) { 165 | return false; 166 | } 167 | } 168 | 169 | public long getLong(final int key) { 170 | try { 171 | return Long.parseLong(getString(key)); 172 | } catch (Exception e) { 173 | return -1; 174 | } 175 | } 176 | 177 | public double getDouble(final int key) { 178 | try { 179 | return Double.parseDouble(getString(key)); 180 | } catch (Exception e) { 181 | return -1; 182 | } 183 | } 184 | 185 | public byte[] getByteArray(final int key) { 186 | return mMeta.get(key); 187 | } 188 | 189 | private boolean checkMetadataId(final int val) { 190 | if (val <= ANY || (LAST_SYSTEM < val && val < FIRST_CUSTOM)) { 191 | return false; 192 | } 193 | return true; 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /android/src/main/java/io/vov/vitamio/ThumbnailUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2006 The Android Open Source Project 3 | * Copyright (C) 2013 YIXIA.COM 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package io.vov.vitamio; 19 | 20 | import android.content.Context; 21 | import android.graphics.Bitmap; 22 | import android.graphics.Canvas; 23 | import android.graphics.Matrix; 24 | import android.graphics.Rect; 25 | import io.vov.vitamio.provider.MediaStore.Video; 26 | 27 | /** 28 | * ThumbnailUtils is a wrapper of MediaMetadataRetriever to retrive a thumbnail 29 | * of video file. 30 | *

31 | *

 32 |  * Bitmap thumb = ThumbnailUtils.createVideoThumbnail(this, videoPath, MINI_KIND);
 33 |  * 
34 | */ 35 | public class ThumbnailUtils { 36 | private static final int OPTIONS_NONE = 0x0; 37 | private static final int OPTIONS_SCALE_UP = 0x1; 38 | public static final int OPTIONS_RECYCLE_INPUT = 0x2; 39 | public static final int TARGET_SIZE_MINI_THUMBNAIL_WIDTH = 426; 40 | public static final int TARGET_SIZE_MINI_THUMBNAIL_HEIGHT = 320; 41 | public static final int TARGET_SIZE_MICRO_THUMBNAIL_WIDTH = 212; 42 | public static final int TARGET_SIZE_MICRO_THUMBNAIL_HEIGHT = 160; 43 | 44 | 45 | public static Bitmap createVideoThumbnail(Context ctx, String filePath, int kind) { 46 | if (!Vitamio.isInitialized(ctx)) { 47 | return null; 48 | } 49 | Bitmap bitmap = null; 50 | MediaMetadataRetriever retriever = null; 51 | try { 52 | retriever = new MediaMetadataRetriever(ctx); 53 | retriever.setDataSource(filePath); 54 | bitmap = retriever.getFrameAtTime(-1); 55 | } catch (Exception ex) { 56 | } finally { 57 | try { 58 | retriever.release(); 59 | } catch (RuntimeException ex) { 60 | } 61 | } 62 | 63 | if (bitmap != null) { 64 | if (kind == Video.Thumbnails.MICRO_KIND) 65 | bitmap = extractThumbnail(bitmap, TARGET_SIZE_MICRO_THUMBNAIL_WIDTH, TARGET_SIZE_MICRO_THUMBNAIL_HEIGHT, OPTIONS_RECYCLE_INPUT); 66 | else if (kind == Video.Thumbnails.MINI_KIND) 67 | bitmap = extractThumbnail(bitmap, TARGET_SIZE_MINI_THUMBNAIL_WIDTH, TARGET_SIZE_MINI_THUMBNAIL_HEIGHT, OPTIONS_RECYCLE_INPUT); 68 | } 69 | return bitmap; 70 | } 71 | 72 | public static Bitmap extractThumbnail(Bitmap source, int width, int height) { 73 | return extractThumbnail(source, width, height, OPTIONS_NONE); 74 | } 75 | 76 | public static Bitmap extractThumbnail(Bitmap source, int width, int height, int options) { 77 | if (source == null) 78 | return null; 79 | 80 | float scale; 81 | if (source.getWidth() < source.getHeight()) 82 | scale = width / (float) source.getWidth(); 83 | else 84 | scale = height / (float) source.getHeight(); 85 | Matrix matrix = new Matrix(); 86 | matrix.setScale(scale, scale); 87 | Bitmap thumbnail = transform(matrix, source, width, height, OPTIONS_SCALE_UP | options); 88 | return thumbnail; 89 | } 90 | 91 | private static Bitmap transform(Matrix scaler, Bitmap source, int targetWidth, int targetHeight, int options) { 92 | boolean scaleUp = (options & OPTIONS_SCALE_UP) != 0; 93 | boolean recycle = (options & OPTIONS_RECYCLE_INPUT) != 0; 94 | 95 | int deltaX = source.getWidth() - targetWidth; 96 | int deltaY = source.getHeight() - targetHeight; 97 | if (!scaleUp && (deltaX < 0 || deltaY < 0)) { 98 | Bitmap b2 = Bitmap.createBitmap(targetWidth, targetHeight, Bitmap.Config.ARGB_8888); 99 | Canvas c = new Canvas(b2); 100 | 101 | int deltaXHalf = Math.max(0, deltaX / 2); 102 | int deltaYHalf = Math.max(0, deltaY / 2); 103 | Rect src = new Rect(deltaXHalf, deltaYHalf, deltaXHalf + Math.min(targetWidth, source.getWidth()), deltaYHalf + Math.min(targetHeight, source.getHeight())); 104 | int dstX = (targetWidth - src.width()) / 2; 105 | int dstY = (targetHeight - src.height()) / 2; 106 | Rect dst = new Rect(dstX, dstY, targetWidth - dstX, targetHeight - dstY); 107 | c.drawBitmap(source, src, dst, null); 108 | if (recycle) 109 | source.recycle(); 110 | return b2; 111 | } 112 | 113 | float bitmapWidthF = source.getWidth(); 114 | float bitmapHeightF = source.getHeight(); 115 | float bitmapAspect = bitmapWidthF / bitmapHeightF; 116 | float viewAspect = (float) targetWidth / targetHeight; 117 | 118 | float scale = bitmapAspect > viewAspect ? targetHeight / bitmapHeightF : targetWidth / bitmapWidthF; 119 | if (scale < .9F || scale > 1F) 120 | scaler.setScale(scale, scale); 121 | else 122 | scaler = null; 123 | 124 | Bitmap b1; 125 | if (scaler != null) 126 | b1 = Bitmap.createBitmap(source, 0, 0, source.getWidth(), source.getHeight(), scaler, true); 127 | else 128 | b1 = source; 129 | 130 | if (recycle && b1 != source) 131 | source.recycle(); 132 | 133 | int dx1 = Math.max(0, b1.getWidth() - targetWidth); 134 | int dy1 = Math.max(0, b1.getHeight() - targetHeight); 135 | 136 | Bitmap b2 = Bitmap.createBitmap(b1, dx1 / 2, dy1 / 2, targetWidth, targetHeight); 137 | 138 | if (b2 != b1 && (recycle || b1 != source)) 139 | b1.recycle(); 140 | 141 | return b2; 142 | } 143 | 144 | } -------------------------------------------------------------------------------- /android/src/main/java/io/vov/vitamio/VIntent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 YIXIA.COM 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.vov.vitamio; 18 | 19 | /** 20 | * Common intent actions used by Vitamio component. 21 | */ 22 | public class VIntent { 23 | public static final String ACTION_MEDIA_SCANNER_SCAN_DIRECTORY = "com.yixia.vitamio.action.MEDIA_SCANNER_SCAN_DIRECTORY"; 24 | public static final String ACTION_MEDIA_SCANNER_SCAN_FILE = "com.yixia.vitamio.action.MEDIA_SCANNER_SCAN_FILE"; 25 | public static final String ACTION_MEDIA_SCANNER_STARTED = "com.yixia.vitamio.action.MEDIA_SCANNER_STARTED"; 26 | public static final String ACTION_MEDIA_SCANNER_FINISHED = "com.yixia.vitamio.action.MEDIA_SCANNER_FINISHED"; 27 | } 28 | -------------------------------------------------------------------------------- /android/src/main/java/io/vov/vitamio/Vitamio.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 YIXIA.COM 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.vov.vitamio; 18 | 19 | 20 | import android.content.Context; 21 | 22 | import io.vov.vitamio.utils.CPU; 23 | import io.vov.vitamio.utils.ContextUtils; 24 | import io.vov.vitamio.utils.IOUtils; 25 | import io.vov.vitamio.utils.Log; 26 | 27 | 28 | 29 | /** 30 | * Inspect this class before using any other Vitamio classes. 31 | *

32 | * Don't modify this class, or the full Vitamio library may be broken. 33 | */ 34 | public class Vitamio { 35 | private static String vitamioPackage; 36 | private static String vitamioLibraryPath; 37 | 38 | /** 39 | * Check if Vitamio is initialized at this device 40 | * 41 | * @param ctx Android Context 42 | * @return true if the Vitamio has been initialized. 43 | */ 44 | public static boolean isInitialized(Context ctx) { 45 | vitamioPackage = ctx.getPackageName(); 46 | vitamioLibraryPath = ContextUtils.getDataDir(ctx) + "lib/"; 47 | return true; 48 | } 49 | 50 | public static String getVitamioPackage() { 51 | return vitamioPackage; 52 | } 53 | 54 | 55 | public static final String getLibraryPath() { 56 | return vitamioLibraryPath; 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /android/src/main/java/io/vov/vitamio/VitamioLicense.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 YIXIA.COM 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.vov.vitamio; 18 | 19 | /** 20 | * DON'T MODIFY THIS FILE IF YOU WANT TO USE VITAMIO 21 | */ 22 | public class VitamioLicense { 23 | public static final String LICENSE = "Copyright (c) YIXIA (http://yixia.com).\nTHIS SOFTWARE (Vitamio) IS WORK OF YIXIA (http://yixia.com)"; 24 | } -------------------------------------------------------------------------------- /android/src/main/java/io/vov/vitamio/provider/MediaStore.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2006 The Android Open Source Project 3 | * Copyright (C) 2013 YIXIA.COM 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package io.vov.vitamio.provider; 19 | 20 | import android.content.ContentResolver; 21 | import android.content.ContentUris; 22 | import android.content.Context; 23 | import android.database.Cursor; 24 | import android.database.sqlite.SQLiteException; 25 | import android.graphics.Bitmap; 26 | import android.graphics.BitmapFactory; 27 | import android.net.Uri; 28 | import android.os.ParcelFileDescriptor; 29 | import android.provider.BaseColumns; 30 | 31 | import io.vov.vitamio.utils.Log; 32 | 33 | import java.io.FileNotFoundException; 34 | import java.io.IOException; 35 | 36 | public final class MediaStore { 37 | public static final String AUTHORITY = "me.abitno.vplayer.mediaprovider"; 38 | public static final String MEDIA_SCANNER_VOLUME = "volume"; 39 | public static final String CONTENT_AUTHORITY_SLASH = "content://" + AUTHORITY + "/"; 40 | public static final Uri CONTENT_URI = Uri.parse(CONTENT_AUTHORITY_SLASH); 41 | private static final String BASE_SQL_FIELDS = MediaColumns._ID + " INTEGER PRIMARY KEY," + // 42 | MediaColumns.DATA + " TEXT NOT NULL," + // 43 | MediaColumns.DIRECTORY + " TEXT NOT NULL," + // 44 | MediaColumns.DIRECTORY_NAME + " TEXT NOT NULL," + // 45 | MediaColumns.SIZE + " INTEGER," + // 46 | MediaColumns.DISPLAY_NAME + " TEXT," + // 47 | MediaColumns.TITLE + " TEXT," + // 48 | MediaColumns.TITLE_KEY + " TEXT," + // 49 | MediaColumns.DATE_ADDED + " INTEGER," + // 50 | MediaColumns.DATE_MODIFIED + " INTEGER," + // 51 | MediaColumns.MIME_TYPE + " TEXT," + // 52 | MediaColumns.AVAILABLE_SIZE + " INTEGER default 0," + // 53 | MediaColumns.PLAY_STATUS + " INTEGER ,"; 54 | 55 | public static Uri getMediaScannerUri() { 56 | return Uri.parse(CONTENT_AUTHORITY_SLASH + "media_scanner"); 57 | } 58 | 59 | public static Uri getVolumeUri() { 60 | return Uri.parse(CONTENT_AUTHORITY_SLASH + MEDIA_SCANNER_VOLUME); 61 | } 62 | 63 | public interface MediaColumns extends BaseColumns { 64 | public static final String DATA = "_data"; 65 | public static final String DIRECTORY = "_directory"; 66 | public static final String DIRECTORY_NAME = "_directory_name"; 67 | public static final String SIZE = "_size"; 68 | public static final String DISPLAY_NAME = "_display_name"; 69 | public static final String TITLE = "title"; 70 | public static final String TITLE_KEY = "title_key"; 71 | public static final String DATE_ADDED = "date_added"; 72 | public static final String DATE_MODIFIED = "date_modified"; 73 | public static final String MIME_TYPE = "mime_type"; 74 | public static final String AVAILABLE_SIZE = "available_size"; 75 | public static final String PLAY_STATUS = "play_status"; 76 | 77 | } 78 | 79 | public static final class Audio { 80 | public interface AudioColumns extends MediaColumns { 81 | public static final String DURATION = "duration"; 82 | public static final String BOOKMARK = "bookmark"; 83 | public static final String ARTIST = "artist"; 84 | public static final String COMPOSER = "composer"; 85 | public static final String ALBUM = "album"; 86 | public static final String TRACK = "track"; 87 | public static final String YEAR = "year"; 88 | } 89 | 90 | public static final class Media implements AudioColumns { 91 | public static final Uri CONTENT_URI = Uri.parse(CONTENT_AUTHORITY_SLASH + "audios/media"); 92 | public static final String CONTENT_TYPE = "vnd.android.cursor.dir/audio"; 93 | } 94 | } 95 | 96 | public static final class Video { 97 | 98 | public interface VideoColumns extends MediaColumns { 99 | public static final String DURATION = "duration"; 100 | public static final String ARTIST = "artist"; 101 | public static final String ALBUM = "album"; 102 | public static final String WIDTH = "width"; 103 | public static final String HEIGHT = "height"; 104 | public static final String DESCRIPTION = "description"; 105 | public static final String LANGUAGE = "language"; 106 | public static final String LATITUDE = "latitude"; 107 | public static final String LONGITUDE = "longitude"; 108 | public static final String DATE_TAKEN = "datetaken"; 109 | public static final String BOOKMARK = "bookmark"; 110 | public static final String MINI_THUMB_MAGIC = "mini_thumb_magic"; 111 | public static final String HIDDEN = "hidden"; 112 | public static final String SUBTRACK = "sub_track"; 113 | public static final String AUDIO_TRACK = "audio_track"; 114 | } 115 | 116 | public static final class Media implements VideoColumns { 117 | public static final Uri CONTENT_URI = Uri.parse(CONTENT_AUTHORITY_SLASH + "videos/media"); 118 | public static final String CONTENT_TYPE = "vnd.android.cursor.dir/video"; 119 | protected static final String TABLE_NAME = "videos"; 120 | protected static final String SQL_FIELDS = BASE_SQL_FIELDS + // 121 | DURATION + " INTEGER," + // 122 | ARTIST + " TEXT," + // 123 | ALBUM + " TEXT," + // 124 | WIDTH + " INTEGER," + // 125 | HEIGHT + " INTEGER," + // 126 | DESCRIPTION + " TEXT," + // 127 | LANGUAGE + " TEXT," + // 128 | LATITUDE + " DOUBLE," + // 129 | LONGITUDE + " DOUBLE," + // 130 | DATE_TAKEN + " INTEGER," + // 131 | BOOKMARK + " INTEGER," + // 132 | MINI_THUMB_MAGIC + " INTEGER," + // 133 | HIDDEN + " INTEGER default 0," + // 1 hidden , 0 visible 134 | SUBTRACK + " TEXT," + // 135 | AUDIO_TRACK + " INTEGER"; 136 | protected static final String SQL_TRIGGER_VIDEO_CLEANUP = "CREATE TRIGGER " + // 137 | "IF NOT EXISTS video_cleanup AFTER DELETE ON " + TABLE_NAME + " " + // 138 | "BEGIN " + // 139 | "SELECT _DELETE_FILE(old._data);" + // 140 | "SELECT _DELETE_FILE(old._data || '.ssi');" + // The cache index 141 | "END"; 142 | protected static final String SQL_TRIGGER_VIDEO_UPDATE = "CREATE TRIGGER " + // 143 | "IF NOT EXISTS video_update AFTER UPDATE ON " + TABLE_NAME + " " + // 144 | "WHEN new._data <> old._data " + // 145 | "BEGIN " + // 146 | "SELECT _DELETE_FILE(old._data || '.ssi');" + // 147 | "END"; 148 | } 149 | 150 | public static class Thumbnails implements BaseColumns { 151 | public static final int MINI_KIND = 1; 152 | public static final int MICRO_KIND = 3; 153 | public static final Uri CONTENT_URI = Uri.parse(CONTENT_AUTHORITY_SLASH + "videos/thumbnails"); 154 | public static final String THUMBNAILS_DIRECTORY = "Android/data/me.abitno.vplayer.t/thumbnails"; 155 | public static final String DATA = "_data"; 156 | public static final String VIDEO_ID = "video_id"; 157 | public static final String KIND = "kind"; 158 | public static final String WIDTH = "width"; 159 | public static final String HEIGHT = "height"; 160 | protected static final String TABLE_NAME = "videothumbnails"; 161 | protected static final String SQL_FIELDS = _ID + " INTEGER PRIMARY KEY," + // 162 | DATA + " TEXT," + // 163 | VIDEO_ID + " INTEGER," + // 164 | KIND + " INTEGER," + // 165 | WIDTH + " INTEGER," + // 166 | HEIGHT + " INTEGER"; 167 | protected static final String SQL_INDEX_VIDEO_ID = "CREATE INDEX IF NOT EXISTS video_id_index on videothumbnails(video_id);"; 168 | protected static final String SQL_TRIGGER_VIDEO_THUMBNAILS_CLEANUP = "CREATE TRIGGER " + // 169 | "IF NOT EXISTS videothumbnails_cleanup DELETE ON videothumbnails " + // 170 | "BEGIN " + // 171 | "SELECT _DELETE_FILE(old._data);" + // 172 | "END"; 173 | 174 | public static void cancelThumbnailRequest(ContentResolver cr, long origId) { 175 | InternalThumbnails.cancelThumbnailRequest(cr, origId, CONTENT_URI, InternalThumbnails.DEFAULT_GROUP_ID); 176 | } 177 | 178 | public static Bitmap getThumbnail(Context ctx, ContentResolver cr, long origId, int kind, BitmapFactory.Options options) { 179 | return InternalThumbnails.getThumbnail(ctx, cr, origId, InternalThumbnails.DEFAULT_GROUP_ID, kind, options, CONTENT_URI); 180 | } 181 | 182 | public static Bitmap getThumbnail(Context ctx, ContentResolver cr, long origId, long groupId, int kind, BitmapFactory.Options options) { 183 | return InternalThumbnails.getThumbnail(ctx, cr, origId, groupId, kind, options, CONTENT_URI); 184 | } 185 | 186 | public static String getThumbnailPath(Context ctx, ContentResolver cr, long origId) { 187 | return InternalThumbnails.getThumbnailPath(ctx, cr, origId, CONTENT_URI); 188 | } 189 | 190 | public static void cancelThumbnailRequest(ContentResolver cr, long origId, long groupId) { 191 | InternalThumbnails.cancelThumbnailRequest(cr, origId, CONTENT_URI, groupId); 192 | } 193 | } 194 | } 195 | 196 | private static class InternalThumbnails implements BaseColumns { 197 | static final int DEFAULT_GROUP_ID = 0; 198 | private static final int MINI_KIND = 1; 199 | private static final int MICRO_KIND = 3; 200 | private static final String[] PROJECTION = new String[]{_ID, MediaColumns.DATA}; 201 | private static final Object sThumbBufLock = new Object(); 202 | private static byte[] sThumbBuf; 203 | 204 | private static Bitmap getMiniThumbFromFile(Cursor c, Uri baseUri, ContentResolver cr, BitmapFactory.Options options) { 205 | Bitmap bitmap = null; 206 | Uri thumbUri = null; 207 | try { 208 | long thumbId = c.getLong(0); 209 | thumbUri = ContentUris.withAppendedId(baseUri, thumbId); 210 | ParcelFileDescriptor pfdInput = cr.openFileDescriptor(thumbUri, "r"); 211 | bitmap = BitmapFactory.decodeFileDescriptor(pfdInput.getFileDescriptor(), null, options); 212 | pfdInput.close(); 213 | } catch (FileNotFoundException ex) { 214 | Log.e("getMiniThumbFromFile", ex); 215 | } catch (IOException ex) { 216 | Log.e("getMiniThumbFromFile", ex); 217 | } catch (OutOfMemoryError ex) { 218 | Log.e("getMiniThumbFromFile", ex); 219 | } 220 | return bitmap; 221 | } 222 | 223 | static void cancelThumbnailRequest(ContentResolver cr, long origId, Uri baseUri, long groupId) { 224 | Uri cancelUri = baseUri.buildUpon().appendQueryParameter("cancel", "1").appendQueryParameter("orig_id", String.valueOf(origId)).appendQueryParameter("group_id", String.valueOf(groupId)).build(); 225 | Cursor c = null; 226 | try { 227 | c = cr.query(cancelUri, PROJECTION, null, null, null); 228 | } finally { 229 | if (c != null) 230 | c.close(); 231 | } 232 | } 233 | 234 | static String getThumbnailPath(Context ctx, ContentResolver cr, long origId, Uri baseUri) { 235 | String column = "video_id="; 236 | String path = ""; 237 | Cursor c = null; 238 | try { 239 | c = cr.query(baseUri, PROJECTION, column + origId, null, null); 240 | if (c != null && c.moveToFirst()) { 241 | path = c.getString(c.getColumnIndex(MediaColumns.DATA)); 242 | } 243 | } finally { 244 | if (c != null) 245 | c.close(); 246 | } 247 | return path; 248 | } 249 | 250 | static Bitmap getThumbnail(Context ctx, ContentResolver cr, long origId, long groupId, int kind, BitmapFactory.Options options, Uri baseUri) { 251 | Bitmap bitmap = null; 252 | MiniThumbFile thumbFile = MiniThumbFile.instance(baseUri); 253 | long magic = thumbFile.getMagic(origId); 254 | if (magic != 0) { 255 | if (kind == MICRO_KIND) { 256 | synchronized (sThumbBufLock) { 257 | if (sThumbBuf == null) 258 | sThumbBuf = new byte[MiniThumbFile.BYTES_PER_MINTHUMB]; 259 | if (thumbFile.getMiniThumbFromFile(origId, sThumbBuf) != null) { 260 | bitmap = BitmapFactory.decodeByteArray(sThumbBuf, 0, sThumbBuf.length); 261 | if (bitmap == null) 262 | Log.d("couldn't decode byte array."); 263 | } 264 | } 265 | return bitmap; 266 | } else if (kind == MINI_KIND) { 267 | String column = "video_id="; 268 | Cursor c = null; 269 | try { 270 | c = cr.query(baseUri, PROJECTION, column + origId, null, null); 271 | if (c != null && c.moveToFirst()) { 272 | bitmap = getMiniThumbFromFile(c, baseUri, cr, options); 273 | if (bitmap != null) 274 | return bitmap; 275 | } 276 | } finally { 277 | if (c != null) 278 | c.close(); 279 | } 280 | } 281 | } 282 | 283 | Cursor c = null; 284 | try { 285 | Uri blockingUri = baseUri.buildUpon().appendQueryParameter("blocking", "1").appendQueryParameter("orig_id", String.valueOf(origId)).appendQueryParameter("group_id", String.valueOf(groupId)).build(); 286 | c = cr.query(blockingUri, PROJECTION, null, null, null); 287 | if (c == null) 288 | return null; 289 | 290 | if (kind == MICRO_KIND) { 291 | synchronized (sThumbBufLock) { 292 | if (sThumbBuf == null) 293 | sThumbBuf = new byte[MiniThumbFile.BYTES_PER_MINTHUMB]; 294 | if (thumbFile.getMiniThumbFromFile(origId, sThumbBuf) != null) { 295 | bitmap = BitmapFactory.decodeByteArray(sThumbBuf, 0, sThumbBuf.length); 296 | if (bitmap == null) 297 | Log.d("couldn't decode byte array."); 298 | } 299 | } 300 | } else if (kind == MINI_KIND) { 301 | if (c.moveToFirst()) 302 | bitmap = getMiniThumbFromFile(c, baseUri, cr, options); 303 | } else { 304 | throw new IllegalArgumentException("Unsupported kind: " + kind); 305 | } 306 | } catch (SQLiteException ex) { 307 | Log.e("getThumbnail", ex); 308 | } finally { 309 | if (c != null) 310 | c.close(); 311 | } 312 | return bitmap; 313 | } 314 | } 315 | 316 | } 317 | -------------------------------------------------------------------------------- /android/src/main/java/io/vov/vitamio/provider/MiniThumbFile.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2006 The Android Open Source Project 3 | * Copyright (C) 2013 YIXIA.COM 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package io.vov.vitamio.provider; 19 | 20 | import android.net.Uri; 21 | import android.os.Environment; 22 | 23 | import io.vov.vitamio.provider.MediaStore.Video; 24 | import io.vov.vitamio.utils.Log; 25 | 26 | import java.io.File; 27 | import java.io.IOException; 28 | import java.io.RandomAccessFile; 29 | import java.nio.ByteBuffer; 30 | import java.nio.channels.FileChannel; 31 | import java.nio.channels.FileLock; 32 | import java.util.Hashtable; 33 | 34 | public class MiniThumbFile { 35 | protected static final int BYTES_PER_MINTHUMB = 10000; 36 | private static final int MINI_THUMB_DATA_FILE_VERSION = 7; 37 | private static final int HEADER_SIZE = 1 + 8 + 4; 38 | private static Hashtable sThumbFiles = new Hashtable(); 39 | private Uri mUri; 40 | private RandomAccessFile mMiniThumbFile; 41 | private FileChannel mChannel; 42 | private ByteBuffer mBuffer; 43 | 44 | public MiniThumbFile(Uri uri) { 45 | mUri = uri; 46 | mBuffer = ByteBuffer.allocateDirect(BYTES_PER_MINTHUMB); 47 | } 48 | 49 | protected static synchronized void reset() { 50 | for (MiniThumbFile file : sThumbFiles.values()) 51 | file.deactivate(); 52 | sThumbFiles.clear(); 53 | } 54 | 55 | protected static synchronized MiniThumbFile instance(Uri uri) { 56 | String type = uri.getPathSegments().get(0); 57 | MiniThumbFile file = sThumbFiles.get(type); 58 | if (file == null) { 59 | file = new MiniThumbFile(Uri.parse(MediaStore.CONTENT_AUTHORITY_SLASH + type + "/media")); 60 | sThumbFiles.put(type, file); 61 | } 62 | 63 | return file; 64 | } 65 | 66 | private String randomAccessFilePath(int version) { 67 | String directoryName = Environment.getExternalStorageDirectory().toString() + "/" + Video.Thumbnails.THUMBNAILS_DIRECTORY; 68 | return directoryName + "/.thumbdata" + version + "-" + mUri.hashCode(); 69 | } 70 | 71 | private void removeOldFile() { 72 | String oldPath = randomAccessFilePath(MINI_THUMB_DATA_FILE_VERSION - 1); 73 | File oldFile = new File(oldPath); 74 | if (oldFile.exists()) { 75 | try { 76 | oldFile.delete(); 77 | } catch (SecurityException ex) { 78 | } 79 | } 80 | } 81 | 82 | private RandomAccessFile miniThumbDataFile() { 83 | if (mMiniThumbFile == null) { 84 | removeOldFile(); 85 | String path = randomAccessFilePath(MINI_THUMB_DATA_FILE_VERSION); 86 | File directory = new File(path).getParentFile(); 87 | if (!directory.isDirectory()) { 88 | if (!directory.mkdirs()) 89 | Log.e("Unable to create .thumbnails directory %s", directory.toString()); 90 | } 91 | File f = new File(path); 92 | try { 93 | mMiniThumbFile = new RandomAccessFile(f, "rw"); 94 | } catch (IOException ex) { 95 | try { 96 | mMiniThumbFile = new RandomAccessFile(f, "r"); 97 | } catch (IOException ex2) { 98 | } 99 | } 100 | 101 | if (mMiniThumbFile != null) 102 | mChannel = mMiniThumbFile.getChannel(); 103 | } 104 | return mMiniThumbFile; 105 | } 106 | 107 | protected synchronized void deactivate() { 108 | if (mMiniThumbFile != null) { 109 | try { 110 | mMiniThumbFile.close(); 111 | mMiniThumbFile = null; 112 | } catch (IOException ex) { 113 | } 114 | } 115 | } 116 | 117 | protected synchronized long getMagic(long id) { 118 | RandomAccessFile r = miniThumbDataFile(); 119 | if (r != null) { 120 | long pos = id * BYTES_PER_MINTHUMB; 121 | FileLock lock = null; 122 | try { 123 | mBuffer.clear(); 124 | mBuffer.limit(1 + 8); 125 | 126 | lock = mChannel.lock(pos, 1 + 8, true); 127 | if (mChannel.read(mBuffer, pos) == 9) { 128 | mBuffer.position(0); 129 | if (mBuffer.get() == 1) 130 | return mBuffer.getLong(); 131 | } 132 | } catch (IOException ex) { 133 | Log.e("Got exception checking file magic: ", ex); 134 | } catch (RuntimeException ex) { 135 | Log.e("Got exception when reading magic, id = %d, disk full or mount read-only? %s", id, ex.getClass().toString()); 136 | } finally { 137 | try { 138 | if (lock != null) 139 | lock.release(); 140 | } catch (IOException ex) { 141 | } 142 | } 143 | } 144 | return 0; 145 | } 146 | 147 | protected synchronized void saveMiniThumbToFile(byte[] data, long id, long magic) throws IOException { 148 | RandomAccessFile r = miniThumbDataFile(); 149 | if (r == null) 150 | return; 151 | 152 | long pos = id * BYTES_PER_MINTHUMB; 153 | FileLock lock = null; 154 | try { 155 | if (data != null) { 156 | if (data.length > BYTES_PER_MINTHUMB - HEADER_SIZE) 157 | return; 158 | 159 | mBuffer.clear(); 160 | mBuffer.put((byte) 1); 161 | mBuffer.putLong(magic); 162 | mBuffer.putInt(data.length); 163 | mBuffer.put(data); 164 | mBuffer.flip(); 165 | 166 | lock = mChannel.lock(pos, BYTES_PER_MINTHUMB, false); 167 | mChannel.write(mBuffer, pos); 168 | } 169 | } catch (IOException ex) { 170 | Log.e("couldn't save mini thumbnail data for %d; %s", id, ex.getMessage()); 171 | throw ex; 172 | } catch (RuntimeException ex) { 173 | Log.e("couldn't save mini thumbnail data for %d, disk full or mount read-only? %s", id, ex.getClass().toString()); 174 | } finally { 175 | try { 176 | if (lock != null) 177 | lock.release(); 178 | } catch (IOException ex) { 179 | } 180 | } 181 | } 182 | 183 | protected synchronized byte[] getMiniThumbFromFile(long id, byte[] data) { 184 | RandomAccessFile r = miniThumbDataFile(); 185 | if (r == null) 186 | return null; 187 | 188 | long pos = id * BYTES_PER_MINTHUMB; 189 | FileLock lock = null; 190 | try { 191 | mBuffer.clear(); 192 | lock = mChannel.lock(pos, BYTES_PER_MINTHUMB, true); 193 | int size = mChannel.read(mBuffer, pos); 194 | if (size > 1 + 8 + 4) { 195 | mBuffer.position(9); 196 | int length = mBuffer.getInt(); 197 | 198 | if (size >= 1 + 8 + 4 + length && data.length >= length) { 199 | mBuffer.get(data, 0, length); 200 | return data; 201 | } 202 | } 203 | } catch (IOException ex) { 204 | Log.e("got exception when reading thumbnail id = %d, exception: %s", id, ex.getMessage()); 205 | } catch (RuntimeException ex) { 206 | Log.e("Got exception when reading thumbnail, id = %d, disk full or mount read-only? %s", id, ex.getClass().toString()); 207 | } finally { 208 | try { 209 | if (lock != null) 210 | lock.release(); 211 | } catch (IOException ex) { 212 | } 213 | } 214 | return null; 215 | } 216 | } -------------------------------------------------------------------------------- /android/src/main/java/io/vov/vitamio/utils/CPU.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 YIXIA.COM 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.vov.vitamio.utils; 17 | 18 | import java.io.BufferedReader; 19 | import java.io.File; 20 | import java.io.FileReader; 21 | import java.io.IOException; 22 | import java.util.HashMap; 23 | import java.util.Map; 24 | 25 | import android.os.Build; 26 | import android.text.TextUtils; 27 | 28 | public class CPU { 29 | private static final Map cpuinfo = new HashMap(); 30 | private static int cachedFeature = -1; 31 | private static String cachedFeatureString = null; 32 | public static final int FEATURE_ARM_V5TE = 1 << 0; 33 | public static final int FEATURE_ARM_V6 = 1 << 1; 34 | public static final int FEATURE_ARM_VFP = 1 << 2; 35 | public static final int FEATURE_ARM_V7A = 1 << 3; 36 | public static final int FEATURE_ARM_VFPV3 = 1 << 4; 37 | public static final int FEATURE_ARM_NEON = 1 << 5; 38 | public static final int FEATURE_X86 = 1 << 6; 39 | public static final int FEATURE_MIPS = 1 << 7; 40 | 41 | public static String getFeatureString() { 42 | getFeature(); 43 | return cachedFeatureString; 44 | } 45 | 46 | public static int getFeature() { 47 | if (cachedFeature > 0) 48 | return getCachedFeature(); 49 | 50 | cachedFeature = FEATURE_ARM_V5TE; 51 | 52 | if (cpuinfo.isEmpty()) { 53 | BufferedReader bis = null; 54 | try { 55 | bis = new BufferedReader(new FileReader(new File("/proc/cpuinfo"))); 56 | String line; 57 | String[] pairs; 58 | while ((line = bis.readLine()) != null) { 59 | if (!line.trim().equals("")) { 60 | pairs = line.split(":"); 61 | if (pairs.length > 1) 62 | cpuinfo.put(pairs[0].trim(), pairs[1].trim()); 63 | } 64 | } 65 | } catch (Exception e) { 66 | Log.e("getCPUFeature", e); 67 | } finally { 68 | try { 69 | if (bis != null) 70 | bis.close(); 71 | } catch (IOException e) { 72 | Log.e("getCPUFeature", e); 73 | } 74 | } 75 | } 76 | 77 | if (!cpuinfo.isEmpty()) { 78 | for (String key : cpuinfo.keySet()) 79 | Log.d("%s:%s", key, cpuinfo.get(key)); 80 | 81 | boolean hasARMv6 = false; 82 | boolean hasARMv7 = false; 83 | 84 | String val = cpuinfo.get("CPU architecture"); 85 | if (!TextUtils.isEmpty(val)) { 86 | try { 87 | int i = StringUtils.convertToInt(val); 88 | Log.d("CPU architecture: %s", i); 89 | if (i >= 7) { 90 | hasARMv6 = true; 91 | hasARMv7 = true; 92 | } else if (i >= 6) { 93 | hasARMv6 = true; 94 | hasARMv7 = false; 95 | } 96 | } catch (NumberFormatException ex) { 97 | Log.e("getCPUFeature", ex); 98 | } 99 | 100 | val = cpuinfo.get("Processor"); 101 | if (TextUtils.isEmpty(val)) { 102 | val = cpuinfo.get("model name"); 103 | } 104 | if (val != null && (val.contains("(v7l)") || val.contains("ARMv7"))) { 105 | hasARMv6 = true; 106 | hasARMv7 = true; 107 | } 108 | if (val != null && (val.contains("(v6l)") || val.contains("ARMv6"))) { 109 | hasARMv6 = true; 110 | hasARMv7 = false; 111 | } 112 | 113 | if (hasARMv6) 114 | cachedFeature |= FEATURE_ARM_V6; 115 | if (hasARMv7) 116 | cachedFeature |= FEATURE_ARM_V7A; 117 | 118 | val = cpuinfo.get("Features"); 119 | if (val != null) { 120 | if (val.contains("neon")) 121 | cachedFeature |= FEATURE_ARM_VFP | FEATURE_ARM_VFPV3 | FEATURE_ARM_NEON; 122 | else if (val.contains("vfpv3")) 123 | cachedFeature |= FEATURE_ARM_VFP | FEATURE_ARM_VFPV3; 124 | else if (val.contains("vfp")) 125 | cachedFeature |= FEATURE_ARM_VFP; 126 | } 127 | } else { 128 | String vendor_id = cpuinfo.get("vendor_id"); 129 | String mips = cpuinfo.get("cpu model"); 130 | if (!TextUtils.isEmpty(vendor_id) && vendor_id.contains("GenuineIntel")) { 131 | cachedFeature |= FEATURE_X86; 132 | } else if (!TextUtils.isEmpty(mips) && mips.contains("MIPS")) { 133 | cachedFeature |= FEATURE_MIPS; 134 | } 135 | } 136 | } 137 | 138 | return getCachedFeature(); 139 | } 140 | 141 | private static int getCachedFeature() { 142 | if (cachedFeatureString == null) { 143 | StringBuffer sb = new StringBuffer(); 144 | if ((cachedFeature & FEATURE_ARM_V5TE) > 0) 145 | sb.append("V5TE "); 146 | if ((cachedFeature & FEATURE_ARM_V6) > 0) 147 | sb.append("V6 "); 148 | if ((cachedFeature & FEATURE_ARM_VFP) > 0) 149 | sb.append("VFP "); 150 | if ((cachedFeature & FEATURE_ARM_V7A) > 0) 151 | sb.append("V7A "); 152 | if ((cachedFeature & FEATURE_ARM_VFPV3) > 0) 153 | sb.append("VFPV3 "); 154 | if ((cachedFeature & FEATURE_ARM_NEON) > 0) 155 | sb.append("NEON "); 156 | if ((cachedFeature & FEATURE_X86) > 0) 157 | sb.append("X86 "); 158 | if ((cachedFeature & FEATURE_MIPS) > 0) 159 | sb.append("MIPS "); 160 | cachedFeatureString = sb.toString(); 161 | } 162 | Log.d("GET CPU FATURE: %s", cachedFeatureString); 163 | return cachedFeature; 164 | } 165 | 166 | public static boolean isDroidXDroid2() { 167 | return (Build.MODEL.trim().equalsIgnoreCase("DROIDX") || Build.MODEL.trim().equalsIgnoreCase("DROID2") || Build.FINGERPRINT.toLowerCase().contains("shadow") || Build.FINGERPRINT.toLowerCase().contains("droid2")); 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /android/src/main/java/io/vov/vitamio/utils/ContextUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 YIXIA.COM 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.vov.vitamio.utils; 17 | 18 | import android.content.Context; 19 | import android.content.pm.ApplicationInfo; 20 | 21 | public class ContextUtils { 22 | public static int getVersionCode(Context ctx) { 23 | int version = 0; 24 | try { 25 | version = ctx.getPackageManager().getPackageInfo(ctx.getApplicationInfo().packageName, 0).versionCode; 26 | } catch (Exception e) { 27 | Log.e("getVersionInt", e); 28 | } 29 | return version; 30 | } 31 | 32 | public static String getDataDir(Context ctx) { 33 | ApplicationInfo ai = ctx.getApplicationInfo(); 34 | if (ai.dataDir != null) 35 | return fixLastSlash(ai.dataDir); 36 | else 37 | return "/data/data/" + ai.packageName + "/"; 38 | } 39 | 40 | public static String fixLastSlash(String str) { 41 | String res = str == null ? "/" : str.trim() + "/"; 42 | if (res.length() > 2 && res.charAt(res.length() - 2) == '/') 43 | res = res.substring(0, res.length() - 1); 44 | return res; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /android/src/main/java/io/vov/vitamio/utils/Crypto.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 YIXIA.COM 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.vov.vitamio.utils; 17 | 18 | import java.io.BufferedInputStream; 19 | import java.io.IOException; 20 | import java.io.InputStream; 21 | import java.io.ObjectInputStream; 22 | import java.io.UnsupportedEncodingException; 23 | import java.math.BigInteger; 24 | import java.security.MessageDigest; 25 | import java.security.PublicKey; 26 | import java.security.spec.AlgorithmParameterSpec; 27 | 28 | import javax.crypto.Cipher; 29 | import javax.crypto.SecretKey; 30 | import javax.crypto.spec.IvParameterSpec; 31 | import javax.crypto.spec.SecretKeySpec; 32 | 33 | public class Crypto { 34 | private Cipher ecipher; 35 | 36 | public Crypto(String key) { 37 | try { 38 | SecretKeySpec skey = new SecretKeySpec(generateKey(key), "AES"); 39 | setupCrypto(skey); 40 | } catch (Exception e) { 41 | Log.e("Crypto", e); 42 | } 43 | } 44 | 45 | private void setupCrypto(SecretKey key) { 46 | byte[] iv = new byte[] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f }; 47 | AlgorithmParameterSpec paramSpec = new IvParameterSpec(iv); 48 | try { 49 | ecipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); 50 | ecipher.init(Cipher.ENCRYPT_MODE, key, paramSpec); 51 | } catch (Exception e) { 52 | ecipher = null; 53 | Log.e("setupCrypto", e); 54 | } 55 | } 56 | 57 | public String encrypt(String plaintext) { 58 | if (ecipher == null) 59 | return ""; 60 | 61 | try { 62 | byte[] ciphertext = ecipher.doFinal(plaintext.getBytes("UTF-8")); 63 | return Base64.encodeToString(ciphertext, Base64.NO_WRAP); 64 | } catch (Exception e) { 65 | Log.e("encryp", e); 66 | return ""; 67 | } 68 | } 69 | 70 | public static String md5(String plain) { 71 | try { 72 | MessageDigest m = MessageDigest.getInstance("MD5"); 73 | m.update(plain.getBytes()); 74 | byte[] digest = m.digest(); 75 | BigInteger bigInt = new BigInteger(1, digest); 76 | String hashtext = bigInt.toString(16); 77 | while (hashtext.length() < 32) { 78 | hashtext = "0" + hashtext; 79 | } 80 | return hashtext; 81 | } catch (Exception e) { 82 | return ""; 83 | } 84 | } 85 | 86 | private static byte[] generateKey(String input) { 87 | try { 88 | byte[] bytesOfMessage = input.getBytes("UTF-8"); 89 | MessageDigest md = MessageDigest.getInstance("SHA256"); 90 | return md.digest(bytesOfMessage); 91 | } catch (Exception e) { 92 | Log.e("generateKey", e); 93 | return null; 94 | } 95 | } 96 | 97 | private PublicKey readKeyFromStream(InputStream keyStream) throws IOException { 98 | ObjectInputStream oin = new ObjectInputStream(new BufferedInputStream(keyStream)); 99 | try { 100 | PublicKey pubKey = (PublicKey) oin.readObject(); 101 | return pubKey; 102 | } catch (Exception e) { 103 | Log.e("readKeyFromStream", e); 104 | return null; 105 | } finally { 106 | oin.close(); 107 | } 108 | } 109 | 110 | public String rsaEncrypt(InputStream keyStream, String data) { 111 | try { 112 | return rsaEncrypt(keyStream, data.getBytes("UTF-8")); 113 | } catch (UnsupportedEncodingException e) { 114 | return ""; 115 | } 116 | } 117 | 118 | public String rsaEncrypt(InputStream keyStream, byte[] data) { 119 | try { 120 | PublicKey pubKey = readKeyFromStream(keyStream); 121 | Cipher cipher = Cipher.getInstance("RSA/ECB/NoPadding"); 122 | cipher.init(Cipher.ENCRYPT_MODE, pubKey); 123 | byte[] cipherData = cipher.doFinal(data); 124 | return Base64.encodeToString(cipherData, Base64.NO_WRAP); 125 | } catch (Exception e) { 126 | Log.e("rsaEncrypt", e); 127 | return ""; 128 | } 129 | } 130 | 131 | } 132 | -------------------------------------------------------------------------------- /android/src/main/java/io/vov/vitamio/utils/Device.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 YIXIA.COM 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.vov.vitamio.utils; 17 | 18 | import android.annotation.SuppressLint; 19 | import android.content.Context; 20 | import android.net.ConnectivityManager; 21 | import android.os.Build; 22 | import android.provider.Settings; 23 | import android.telephony.TelephonyManager; 24 | import android.util.DisplayMetrics; 25 | 26 | import java.util.Locale; 27 | 28 | public class Device { 29 | public static String getLocale() { 30 | Locale locale = Locale.getDefault(); 31 | if (locale != null) { 32 | String lo = locale.getLanguage(); 33 | Log.i("getLocale " + lo); 34 | if (lo != null) { 35 | return lo.toLowerCase(); 36 | } 37 | } 38 | return "en"; 39 | } 40 | 41 | public static String getDeviceFeatures(Context ctx) { 42 | return getIdentifiers(ctx) + getSystemFeatures() + getScreenFeatures(ctx); 43 | } 44 | 45 | @SuppressLint("NewApi") 46 | public static String getIdentifiers(Context ctx) { 47 | StringBuilder sb = new StringBuilder(); 48 | if (Build.VERSION.SDK_INT > Build.VERSION_CODES.FROYO) 49 | sb.append(getPair("serial", Build.SERIAL)); 50 | else 51 | sb.append(getPair("serial", "No Serial")); 52 | sb.append(getPair("android_id", Settings.Secure.getString(ctx.getContentResolver(), Settings.Secure.ANDROID_ID))); 53 | TelephonyManager tel = (TelephonyManager) ctx.getSystemService(Context.TELEPHONY_SERVICE); 54 | sb.append(getPair("sim_country_iso", tel.getSimCountryIso())); 55 | sb.append(getPair("network_operator_name", tel.getNetworkOperatorName())); 56 | sb.append(getPair("unique_id", Crypto.md5(sb.toString()))); 57 | ConnectivityManager cm = (ConnectivityManager) ctx.getSystemService(Context.CONNECTIVITY_SERVICE); 58 | sb.append(getPair("network_type", cm.getActiveNetworkInfo() == null ? "-1" : String.valueOf(cm.getActiveNetworkInfo().getType()))); 59 | return sb.toString(); 60 | } 61 | 62 | public static String getSystemFeatures() { 63 | StringBuilder sb = new StringBuilder(); 64 | sb.append(getPair("android_release", Build.VERSION.RELEASE)); 65 | sb.append(getPair("android_sdk_int", "" + Build.VERSION.SDK_INT)); 66 | sb.append(getPair("device_cpu_abi", Build.CPU_ABI)); 67 | sb.append(getPair("device_model", Build.MODEL)); 68 | sb.append(getPair("device_manufacturer", Build.MANUFACTURER)); 69 | sb.append(getPair("device_board", Build.BOARD)); 70 | sb.append(getPair("device_fingerprint", Build.FINGERPRINT)); 71 | sb.append(getPair("device_cpu_feature", CPU.getFeatureString())); 72 | return sb.toString(); 73 | } 74 | 75 | public static String getScreenFeatures(Context ctx) { 76 | StringBuilder sb = new StringBuilder(); 77 | DisplayMetrics disp = ctx.getResources().getDisplayMetrics(); 78 | sb.append(getPair("screen_density", "" + disp.density)); 79 | sb.append(getPair("screen_density_dpi", "" + disp.densityDpi)); 80 | sb.append(getPair("screen_height_pixels", "" + disp.heightPixels)); 81 | sb.append(getPair("screen_width_pixels", "" + disp.widthPixels)); 82 | sb.append(getPair("screen_scaled_density", "" + disp.scaledDensity)); 83 | sb.append(getPair("screen_xdpi", "" + disp.xdpi)); 84 | sb.append(getPair("screen_ydpi", "" + disp.ydpi)); 85 | return sb.toString(); 86 | } 87 | 88 | private static String getPair(String key, String value) { 89 | key = key == null ? "" : key.trim(); 90 | value = value == null ? "" : value.trim(); 91 | return "&" + key + "=" + value; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /android/src/main/java/io/vov/vitamio/utils/FileUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 YIXIA.COM 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.vov.vitamio.utils; 17 | 18 | import android.net.Uri; 19 | import android.text.TextUtils; 20 | 21 | import java.io.File; 22 | import java.io.IOException; 23 | 24 | public class FileUtils { 25 | private static final String FILE_NAME_RESERVED = "|\\?*<\":>+[]/'"; 26 | 27 | public static String getUniqueFileName(String name, String id) { 28 | StringBuilder sb = new StringBuilder(); 29 | for (Character c : name.toCharArray()) { 30 | if (FILE_NAME_RESERVED.indexOf(c) == -1) { 31 | sb.append(c); 32 | } 33 | } 34 | name = sb.toString(); 35 | if (name.length() > 16) { 36 | name = name.substring(0, 16); 37 | } 38 | id = Crypto.md5(id); 39 | name += id; 40 | try { 41 | File f = File.createTempFile(name, null); 42 | if (f.exists()) { 43 | f.delete(); 44 | return name; 45 | } 46 | } catch (IOException e) { 47 | } 48 | return id; 49 | } 50 | 51 | public static String getCanonical(File f) { 52 | if (f == null) 53 | return null; 54 | 55 | try { 56 | return f.getCanonicalPath(); 57 | } catch (IOException e) { 58 | return f.getAbsolutePath(); 59 | } 60 | } 61 | 62 | /** 63 | * Get the path for the file:/// only 64 | * 65 | * @param uri 66 | * @return 67 | */ 68 | public static String getPath(String uri) { 69 | Log.i("FileUtils#getPath(%s)", uri); 70 | if (TextUtils.isEmpty(uri)) 71 | return null; 72 | if (uri.startsWith("file://") && uri.length() > 7) 73 | return Uri.decode(uri.substring(7)); 74 | return Uri.decode(uri); 75 | } 76 | 77 | public static String getName(String uri) { 78 | String path = getPath(uri); 79 | if (path != null) 80 | return new File(path).getName(); 81 | return null; 82 | } 83 | 84 | public static void deleteDir(File f) { 85 | if (f.exists() && f.isDirectory()) { 86 | for (File file : f.listFiles()) { 87 | if (file.isDirectory()) 88 | deleteDir(file); 89 | file.delete(); 90 | } 91 | f.delete(); 92 | } 93 | } 94 | } -------------------------------------------------------------------------------- /android/src/main/java/io/vov/vitamio/utils/IOUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 YIXIA.COM 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.vov.vitamio.utils; 17 | 18 | import android.database.Cursor; 19 | import android.os.ParcelFileDescriptor; 20 | import android.util.Log; 21 | 22 | import java.io.Closeable; 23 | 24 | public class IOUtils { 25 | 26 | private static final String TAG = "IOUtils"; 27 | 28 | public static void closeSilently(Closeable c) { 29 | if (c == null) 30 | return; 31 | try { 32 | c.close(); 33 | } catch (Throwable t) { 34 | Log.w(TAG, "fail to close", t); 35 | } 36 | } 37 | 38 | public static void closeSilently(ParcelFileDescriptor c) { 39 | if (c == null) 40 | return; 41 | try { 42 | c.close(); 43 | } catch (Throwable t) { 44 | Log.w(TAG, "fail to close", t); 45 | } 46 | } 47 | 48 | public static void closeSilently(Cursor cursor) { 49 | try { 50 | if (cursor != null) cursor.close(); 51 | } catch (Throwable t) { 52 | Log.w(TAG, "fail to close", t); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /android/src/main/java/io/vov/vitamio/utils/Log.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 YIXIA.COM 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.vov.vitamio.utils; 17 | 18 | import java.util.MissingFormatArgumentException; 19 | 20 | public class Log { 21 | public static final String TAG = "Vitamio[Player]"; 22 | 23 | public static void i(String msg, Object... args) { 24 | } 25 | 26 | public static void d(String msg, Object... args) { 27 | 28 | } 29 | 30 | public static void e(String msg, Object... args) { 31 | try { 32 | android.util.Log.e(TAG, String.format(msg, args)); 33 | } catch (MissingFormatArgumentException e) { 34 | android.util.Log.e(TAG, "vitamio.Log", e); 35 | android.util.Log.e(TAG, msg); 36 | } 37 | } 38 | 39 | public static void e(String msg, Throwable t) { 40 | android.util.Log.e(TAG, msg, t); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /android/src/main/java/io/vov/vitamio/utils/ScreenResolution.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2006 The Android Open Source Project 3 | * Copyright (C) 2013 YIXIA.COM 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package io.vov.vitamio.utils; 18 | 19 | import android.annotation.TargetApi; 20 | import android.content.Context; 21 | import android.os.Build; 22 | import android.util.DisplayMetrics; 23 | import android.util.Pair; 24 | import android.view.Display; 25 | import android.view.WindowManager; 26 | 27 | import java.lang.reflect.Method; 28 | 29 | /** 30 | * Class to get the real screen resolution includes the system status bar. 31 | * We can get the value by calling the getRealMetrics method if API >= 17 32 | * Reflection needed on old devices.. 33 | * */ 34 | public class ScreenResolution { 35 | /** 36 | * Gets the resolution, 37 | * @return a pair to return the width and height 38 | * */ 39 | public static Pair getResolution(Context ctx){ 40 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { 41 | return getRealResolution(ctx); 42 | } 43 | else { 44 | return getRealResolutionOnOldDevice(ctx); 45 | } 46 | } 47 | 48 | /** 49 | * Gets resolution on old devices. 50 | * Tries the reflection to get the real resolution first. 51 | * Fall back to getDisplayMetrics if the above method failed. 52 | * */ 53 | private static Pair getRealResolutionOnOldDevice(Context ctx) { 54 | try{ 55 | WindowManager wm = (WindowManager) ctx.getSystemService(Context.WINDOW_SERVICE); 56 | Display display = wm.getDefaultDisplay(); 57 | Method mGetRawWidth = Display.class.getMethod("getRawWidth"); 58 | Method mGetRawHeight = Display.class.getMethod("getRawHeight"); 59 | Integer realWidth = (Integer) mGetRawWidth.invoke(display); 60 | Integer realHeight = (Integer) mGetRawHeight.invoke(display); 61 | return new Pair(realWidth, realHeight); 62 | } 63 | catch (Exception e) { 64 | DisplayMetrics disp = ctx.getResources().getDisplayMetrics(); 65 | return new Pair(disp.widthPixels, disp.heightPixels); 66 | } 67 | } 68 | 69 | /** 70 | * Gets real resolution via the new getRealMetrics API. 71 | * */ 72 | @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) 73 | private static Pair getRealResolution(Context ctx) { 74 | WindowManager wm = (WindowManager) ctx.getSystemService(Context.WINDOW_SERVICE); 75 | Display display = wm.getDefaultDisplay(); 76 | DisplayMetrics metrics = new DisplayMetrics(); 77 | display.getRealMetrics(metrics); 78 | return new Pair(metrics.widthPixels, metrics.heightPixels); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /android/src/main/java/io/vov/vitamio/utils/StringUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 YIXIA.COM 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.vov.vitamio.utils; 17 | 18 | import java.util.Arrays; 19 | import java.util.Iterator; 20 | 21 | public class StringUtils { 22 | public static String join(Object[] elements, CharSequence separator) { 23 | return join(Arrays.asList(elements), separator); 24 | } 25 | 26 | public static String join(Iterable elements, CharSequence separator) { 27 | StringBuilder builder = new StringBuilder(); 28 | 29 | if (elements != null) { 30 | Iterator iter = elements.iterator(); 31 | if (iter.hasNext()) { 32 | builder.append(String.valueOf(iter.next())); 33 | while (iter.hasNext()) { 34 | builder.append(separator).append(String.valueOf(iter.next())); 35 | } 36 | } 37 | } 38 | 39 | return builder.toString(); 40 | } 41 | 42 | public static String fixLastSlash(String str) { 43 | String res = str == null ? "/" : str.trim() + "/"; 44 | if (res.length() > 2 && res.charAt(res.length() - 2) == '/') 45 | res = res.substring(0, res.length() - 1); 46 | return res; 47 | } 48 | 49 | public static int convertToInt(String str) throws NumberFormatException { 50 | int s, e; 51 | for (s = 0; s < str.length(); s++) 52 | if (Character.isDigit(str.charAt(s))) 53 | break; 54 | for (e = str.length(); e > 0; e--) 55 | if (Character.isDigit(str.charAt(e - 1))) 56 | break; 57 | if (e > s) { 58 | try { 59 | return Integer.parseInt(str.substring(s, e)); 60 | } catch (NumberFormatException ex) { 61 | Log.e("convertToInt", ex); 62 | throw new NumberFormatException(); 63 | } 64 | } else { 65 | throw new NumberFormatException(); 66 | } 67 | } 68 | 69 | public static String generateTime(long time) { 70 | int totalSeconds = (int) (time / 1000); 71 | int seconds = totalSeconds % 60; 72 | int minutes = (totalSeconds / 60) % 60; 73 | int hours = totalSeconds / 3600; 74 | 75 | return hours > 0 ? String.format("%02d:%02d:%02d", hours, minutes, seconds) : String.format("%02d:%02d", minutes, seconds); 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const React = require('react'); 2 | const { 3 | PropTypes, 4 | Component, 5 | } = React; 6 | const ReactNative = require('react-native'); 7 | const { 8 | StyleSheet, 9 | requireNativeComponent, 10 | NativeModules, 11 | View, 12 | } = ReactNative; 13 | 14 | const styles = StyleSheet.create({ 15 | base: { 16 | overflow: 'hidden', 17 | }, 18 | }); 19 | 20 | export default class Video extends Component { 21 | 22 | constructor (props, context) { 23 | super(props, context); 24 | this.seek = this.seek.bind(this); 25 | this._assignRoot = this._assignRoot.bind(this); 26 | this._onLoadStart = this._onLoadStart.bind(this); 27 | this._onLoad = this._onLoad.bind(this); 28 | this._onError = this._onError.bind(this); 29 | this._onProgress = this._onProgress.bind(this); 30 | this._onSeek = this._onSeek.bind(this); 31 | this._onEnd = this._onEnd.bind(this); 32 | } 33 | 34 | setNativeProps (nativeProps) { 35 | this._root.setNativeProps(nativeProps); 36 | } 37 | 38 | seek (time) { 39 | this.setNativeProps({ seek: time }); 40 | } 41 | 42 | _assignRoot (component) { 43 | this._root = component; 44 | } 45 | 46 | _onLoadStart (event) { 47 | if (this.props.onLoadStart) { 48 | this.props.onLoadStart(event.nativeEvent); 49 | } 50 | } 51 | 52 | _onLoad (event) { 53 | if (this.props.onLoad) { 54 | this.props.onLoad(event.nativeEvent); 55 | } 56 | } 57 | 58 | _onError (event) { 59 | if (this.props.onError) { 60 | this.props.onError(event.nativeEvent); 61 | } 62 | } 63 | 64 | _onProgress (event) { 65 | if (this.props.onProgress) { 66 | this.props.onProgress(event.nativeEvent); 67 | } 68 | } 69 | 70 | _onSeek (event) { 71 | if (this.props.onSeek) { 72 | this.props.onSeek(event.nativeEvent); 73 | } 74 | } 75 | 76 | _onEnd (event) { 77 | if (this.props.onEnd) { 78 | this.props.onEnd(event.nativeEvent); 79 | } 80 | } 81 | 82 | render () { 83 | const { 84 | source, 85 | resizeMode, 86 | } = this.props; 87 | 88 | let uri = source.uri; 89 | if (uri && uri.match(/^\//)) { 90 | uri = `file://${uri}`; 91 | } 92 | 93 | const isNetwork = !!(uri && uri.match(/^https?:/)); 94 | const isAsset = !!(uri && uri.match(/^(assets-library|file):/)); 95 | 96 | let nativeResizeMode; 97 | if (resizeMode === 'stretch') { 98 | nativeResizeMode = NativeModules.UIManager.RCTVideo.Constants.ScaleToFill; 99 | } else if (resizeMode === 'contain') { 100 | nativeResizeMode = NativeModules.UIManager.RCTVideo.Constants.ScaleAspectFit; 101 | } else if (resizeMode === 'cover') { 102 | nativeResizeMode = NativeModules.UIManager.RCTVideo.Constants.ScaleAspectFill; 103 | } else { 104 | nativeResizeMode = NativeModules.UIManager.RCTVideo.Constants.ScaleNone; 105 | } 106 | 107 | const nativeProps = Object.assign({}, this.props); 108 | Object.assign(nativeProps, { 109 | style: [styles.base, nativeProps.style], 110 | resizeMode: nativeResizeMode, 111 | src: { 112 | uri, 113 | isNetwork, 114 | isAsset, 115 | type: source.type || 'mp4', 116 | }, 117 | onVideoLoadStart: this._onLoadStart, 118 | onVideoLoad: this._onLoad, 119 | onVideoError: this._onError, 120 | onVideoProgress: this._onProgress, 121 | onVideoSeek: this._onSeek, 122 | onVideoEnd: this._onEnd, 123 | }); 124 | 125 | return ( 126 | 130 | ); 131 | } 132 | } 133 | 134 | Video.propTypes = { 135 | /* Native only */ 136 | src: PropTypes.object, 137 | seek: PropTypes.number, 138 | 139 | /* Wrapper component */ 140 | source: PropTypes.object, 141 | resizeMode: PropTypes.string, 142 | repeat: PropTypes.bool, 143 | paused: PropTypes.bool, 144 | muted: PropTypes.bool, 145 | volume: PropTypes.number, 146 | rate: PropTypes.number, 147 | controls: PropTypes.bool, 148 | currentTime: PropTypes.number, 149 | onLoadStart: PropTypes.func, 150 | onLoad: PropTypes.func, 151 | onError: PropTypes.func, 152 | onProgress: PropTypes.func, 153 | onSeek: PropTypes.func, 154 | onEnd: PropTypes.func, 155 | 156 | /* Required by react-native */ 157 | scaleX: React.PropTypes.number, 158 | scaleY: React.PropTypes.number, 159 | translateX: React.PropTypes.number, 160 | translateY: React.PropTypes.number, 161 | rotation: React.PropTypes.number, 162 | ...View.propTypes, 163 | }; 164 | 165 | const RCTVideo = requireNativeComponent('RCTVideo', Video, { 166 | nativeOnly: { 167 | src: true, 168 | seek: true, 169 | }, 170 | }); 171 | -------------------------------------------------------------------------------- /ios/RCTVideo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 3201A4661C7DB61100AA7980 /* RCTVideoManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 3201A4651C7DB61100AA7980 /* RCTVideoManager.m */; }; 11 | 32D980E11BE9F11C00FA27E5 /* RCTVideo.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 32D980E01BE9F11C00FA27E5 /* RCTVideo.h */; }; 12 | 32D980E31BE9F11C00FA27E5 /* RCTVideo.m in Sources */ = {isa = PBXBuildFile; fileRef = 32D980E21BE9F11C00FA27E5 /* RCTVideo.m */; }; 13 | /* End PBXBuildFile section */ 14 | 15 | /* Begin PBXCopyFilesBuildPhase section */ 16 | 32D980DB1BE9F11C00FA27E5 /* CopyFiles */ = { 17 | isa = PBXCopyFilesBuildPhase; 18 | buildActionMask = 2147483647; 19 | dstPath = "include/$(PRODUCT_NAME)"; 20 | dstSubfolderSpec = 16; 21 | files = ( 22 | 32D980E11BE9F11C00FA27E5 /* RCTVideo.h in CopyFiles */, 23 | ); 24 | runOnlyForDeploymentPostprocessing = 0; 25 | }; 26 | /* End PBXCopyFilesBuildPhase section */ 27 | 28 | /* Begin PBXFileReference section */ 29 | 3201A4641C7DB61100AA7980 /* RCTVideoManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTVideoManager.h; sourceTree = ""; }; 30 | 3201A4651C7DB61100AA7980 /* RCTVideoManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTVideoManager.m; sourceTree = ""; }; 31 | 32D980DD1BE9F11C00FA27E5 /* libRCTVideo.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTVideo.a; sourceTree = BUILT_PRODUCTS_DIR; }; 32 | 32D980E01BE9F11C00FA27E5 /* RCTVideo.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTVideo.h; sourceTree = ""; }; 33 | 32D980E21BE9F11C00FA27E5 /* RCTVideo.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RCTVideo.m; sourceTree = ""; }; 34 | /* End PBXFileReference section */ 35 | 36 | /* Begin PBXFrameworksBuildPhase section */ 37 | 32D980DA1BE9F11C00FA27E5 /* Frameworks */ = { 38 | isa = PBXFrameworksBuildPhase; 39 | buildActionMask = 2147483647; 40 | files = ( 41 | ); 42 | runOnlyForDeploymentPostprocessing = 0; 43 | }; 44 | /* End PBXFrameworksBuildPhase section */ 45 | 46 | /* Begin PBXGroup section */ 47 | 32D980D41BE9F11C00FA27E5 = { 48 | isa = PBXGroup; 49 | children = ( 50 | 32D980DF1BE9F11C00FA27E5 /* RCTVideo */, 51 | 32D980DE1BE9F11C00FA27E5 /* Products */, 52 | ); 53 | sourceTree = ""; 54 | }; 55 | 32D980DE1BE9F11C00FA27E5 /* Products */ = { 56 | isa = PBXGroup; 57 | children = ( 58 | 32D980DD1BE9F11C00FA27E5 /* libRCTVideo.a */, 59 | ); 60 | name = Products; 61 | sourceTree = ""; 62 | }; 63 | 32D980DF1BE9F11C00FA27E5 /* RCTVideo */ = { 64 | isa = PBXGroup; 65 | children = ( 66 | 32D980E01BE9F11C00FA27E5 /* RCTVideo.h */, 67 | 3201A4641C7DB61100AA7980 /* RCTVideoManager.h */, 68 | 3201A4651C7DB61100AA7980 /* RCTVideoManager.m */, 69 | 32D980E21BE9F11C00FA27E5 /* RCTVideo.m */, 70 | ); 71 | path = RCTVideo; 72 | sourceTree = ""; 73 | }; 74 | /* End PBXGroup section */ 75 | 76 | /* Begin PBXNativeTarget section */ 77 | 32D980DC1BE9F11C00FA27E5 /* RCTVideo */ = { 78 | isa = PBXNativeTarget; 79 | buildConfigurationList = 32D980F11BE9F11C00FA27E5 /* Build configuration list for PBXNativeTarget "RCTVideo" */; 80 | buildPhases = ( 81 | 32D980D91BE9F11C00FA27E5 /* Sources */, 82 | 32D980DA1BE9F11C00FA27E5 /* Frameworks */, 83 | 32D980DB1BE9F11C00FA27E5 /* CopyFiles */, 84 | ); 85 | buildRules = ( 86 | ); 87 | dependencies = ( 88 | ); 89 | name = RCTVideo; 90 | productName = RCTVideo; 91 | productReference = 32D980DD1BE9F11C00FA27E5 /* libRCTVideo.a */; 92 | productType = "com.apple.product-type.library.static"; 93 | }; 94 | /* End PBXNativeTarget section */ 95 | 96 | /* Begin PBXProject section */ 97 | 32D980D51BE9F11C00FA27E5 /* Project object */ = { 98 | isa = PBXProject; 99 | attributes = { 100 | LastUpgradeCheck = 0640; 101 | ORGANIZATIONNAME = remobile; 102 | TargetAttributes = { 103 | 32D980DC1BE9F11C00FA27E5 = { 104 | CreatedOnToolsVersion = 6.4; 105 | }; 106 | }; 107 | }; 108 | buildConfigurationList = 32D980D81BE9F11C00FA27E5 /* Build configuration list for PBXProject "RCTVideo" */; 109 | compatibilityVersion = "Xcode 3.2"; 110 | developmentRegion = English; 111 | hasScannedForEncodings = 0; 112 | knownRegions = ( 113 | en, 114 | ); 115 | mainGroup = 32D980D41BE9F11C00FA27E5; 116 | productRefGroup = 32D980DE1BE9F11C00FA27E5 /* Products */; 117 | projectDirPath = ""; 118 | projectRoot = ""; 119 | targets = ( 120 | 32D980DC1BE9F11C00FA27E5 /* RCTVideo */, 121 | ); 122 | }; 123 | /* End PBXProject section */ 124 | 125 | /* Begin PBXSourcesBuildPhase section */ 126 | 32D980D91BE9F11C00FA27E5 /* Sources */ = { 127 | isa = PBXSourcesBuildPhase; 128 | buildActionMask = 2147483647; 129 | files = ( 130 | 32D980E31BE9F11C00FA27E5 /* RCTVideo.m in Sources */, 131 | 3201A4661C7DB61100AA7980 /* RCTVideoManager.m in Sources */, 132 | ); 133 | runOnlyForDeploymentPostprocessing = 0; 134 | }; 135 | /* End PBXSourcesBuildPhase section */ 136 | 137 | /* Begin XCBuildConfiguration section */ 138 | 32D980EF1BE9F11C00FA27E5 /* Debug */ = { 139 | isa = XCBuildConfiguration; 140 | buildSettings = { 141 | ALWAYS_SEARCH_USER_PATHS = NO; 142 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 143 | CLANG_CXX_LIBRARY = "libc++"; 144 | CLANG_ENABLE_MODULES = YES; 145 | CLANG_ENABLE_OBJC_ARC = YES; 146 | CLANG_WARN_BOOL_CONVERSION = YES; 147 | CLANG_WARN_CONSTANT_CONVERSION = YES; 148 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 149 | CLANG_WARN_EMPTY_BODY = YES; 150 | CLANG_WARN_ENUM_CONVERSION = YES; 151 | CLANG_WARN_INT_CONVERSION = YES; 152 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 153 | CLANG_WARN_UNREACHABLE_CODE = YES; 154 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 155 | COPY_PHASE_STRIP = NO; 156 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 157 | ENABLE_STRICT_OBJC_MSGSEND = YES; 158 | GCC_C_LANGUAGE_STANDARD = gnu99; 159 | GCC_DYNAMIC_NO_PIC = NO; 160 | GCC_NO_COMMON_BLOCKS = YES; 161 | GCC_OPTIMIZATION_LEVEL = 0; 162 | GCC_PREPROCESSOR_DEFINITIONS = ( 163 | "DEBUG=1", 164 | "$(inherited)", 165 | ); 166 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 167 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 168 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 169 | GCC_WARN_UNDECLARED_SELECTOR = YES; 170 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 171 | GCC_WARN_UNUSED_FUNCTION = YES; 172 | GCC_WARN_UNUSED_VARIABLE = YES; 173 | IPHONEOS_DEPLOYMENT_TARGET = 7.0; 174 | MTL_ENABLE_DEBUG_INFO = YES; 175 | ONLY_ACTIVE_ARCH = YES; 176 | SDKROOT = iphoneos; 177 | }; 178 | name = Debug; 179 | }; 180 | 32D980F01BE9F11C00FA27E5 /* Release */ = { 181 | isa = XCBuildConfiguration; 182 | buildSettings = { 183 | ALWAYS_SEARCH_USER_PATHS = NO; 184 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 185 | CLANG_CXX_LIBRARY = "libc++"; 186 | CLANG_ENABLE_MODULES = YES; 187 | CLANG_ENABLE_OBJC_ARC = YES; 188 | CLANG_WARN_BOOL_CONVERSION = YES; 189 | CLANG_WARN_CONSTANT_CONVERSION = YES; 190 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 191 | CLANG_WARN_EMPTY_BODY = YES; 192 | CLANG_WARN_ENUM_CONVERSION = YES; 193 | CLANG_WARN_INT_CONVERSION = YES; 194 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 195 | CLANG_WARN_UNREACHABLE_CODE = YES; 196 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 197 | COPY_PHASE_STRIP = NO; 198 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 199 | ENABLE_NS_ASSERTIONS = NO; 200 | ENABLE_STRICT_OBJC_MSGSEND = YES; 201 | GCC_C_LANGUAGE_STANDARD = gnu99; 202 | GCC_NO_COMMON_BLOCKS = YES; 203 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 204 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 205 | GCC_WARN_UNDECLARED_SELECTOR = YES; 206 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 207 | GCC_WARN_UNUSED_FUNCTION = YES; 208 | GCC_WARN_UNUSED_VARIABLE = YES; 209 | IPHONEOS_DEPLOYMENT_TARGET = 7.0; 210 | MTL_ENABLE_DEBUG_INFO = NO; 211 | SDKROOT = iphoneos; 212 | VALIDATE_PRODUCT = YES; 213 | }; 214 | name = Release; 215 | }; 216 | 32D980F21BE9F11C00FA27E5 /* Debug */ = { 217 | isa = XCBuildConfiguration; 218 | buildSettings = { 219 | HEADER_SEARCH_PATHS = ( 220 | "$(inherited)", 221 | /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, 222 | ); 223 | OTHER_LDFLAGS = "-ObjC"; 224 | PRODUCT_NAME = "$(TARGET_NAME)"; 225 | SKIP_INSTALL = YES; 226 | }; 227 | name = Debug; 228 | }; 229 | 32D980F31BE9F11C00FA27E5 /* Release */ = { 230 | isa = XCBuildConfiguration; 231 | buildSettings = { 232 | HEADER_SEARCH_PATHS = ( 233 | "$(inherited)", 234 | /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, 235 | ); 236 | OTHER_LDFLAGS = "-ObjC"; 237 | PRODUCT_NAME = "$(TARGET_NAME)"; 238 | SKIP_INSTALL = YES; 239 | }; 240 | name = Release; 241 | }; 242 | /* End XCBuildConfiguration section */ 243 | 244 | /* Begin XCConfigurationList section */ 245 | 32D980D81BE9F11C00FA27E5 /* Build configuration list for PBXProject "RCTVideo" */ = { 246 | isa = XCConfigurationList; 247 | buildConfigurations = ( 248 | 32D980EF1BE9F11C00FA27E5 /* Debug */, 249 | 32D980F01BE9F11C00FA27E5 /* Release */, 250 | ); 251 | defaultConfigurationIsVisible = 0; 252 | defaultConfigurationName = Release; 253 | }; 254 | 32D980F11BE9F11C00FA27E5 /* Build configuration list for PBXNativeTarget "RCTVideo" */ = { 255 | isa = XCConfigurationList; 256 | buildConfigurations = ( 257 | 32D980F21BE9F11C00FA27E5 /* Debug */, 258 | 32D980F31BE9F11C00FA27E5 /* Release */, 259 | ); 260 | defaultConfigurationIsVisible = 0; 261 | defaultConfigurationName = Release; 262 | }; 263 | /* End XCConfigurationList section */ 264 | }; 265 | rootObject = 32D980D51BE9F11C00FA27E5 /* Project object */; 266 | } 267 | -------------------------------------------------------------------------------- /ios/RCTVideo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/RCTVideo/RCTVideo.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import "AVKit/AVKit.h" 4 | 5 | @class RCTEventDispatcher; 6 | 7 | @interface RCTVideo : UIView 8 | 9 | - (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER; 10 | 11 | - (AVPlayerViewController*)createPlayerViewController:(AVPlayer*)player withPlayerItem:(AVPlayerItem*)playerItem; 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /ios/RCTVideo/RCTVideo.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import 4 | #import 5 | #import "RCTVideo.h" 6 | 7 | static NSString *const statusKeyPath = @"status"; 8 | static NSString *const playbackLikelyToKeepUpKeyPath = @"playbackLikelyToKeepUp"; 9 | static NSString *const playbackBufferEmptyKeyPath = @"playbackBufferEmpty"; 10 | 11 | @implementation RCTVideo 12 | { 13 | AVPlayer *_player; 14 | AVPlayerItem *_playerItem; 15 | BOOL _playerItemObserversSet; 16 | BOOL _playerBufferEmpty; 17 | AVPlayerLayer *_playerLayer; 18 | AVPlayerViewController *_playerViewController; 19 | NSURL *_videoURL; 20 | 21 | /* Required to publish events */ 22 | RCTEventDispatcher *_eventDispatcher; 23 | 24 | bool _pendingSeek; 25 | float _pendingSeekTime; 26 | float _lastSeekTime; 27 | 28 | /* For sending videoProgress events */ 29 | Float64 _progressUpdateInterval; 30 | BOOL _controls; 31 | id _timeObserver; 32 | 33 | /* Keep track of any modifiers, need to be applied after each play */ 34 | float _volume; 35 | float _rate; 36 | BOOL _muted; 37 | BOOL _paused; 38 | BOOL _repeat; 39 | NSString * _resizeMode; 40 | } 41 | 42 | - (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher 43 | { 44 | if ((self = [super init])) { 45 | _eventDispatcher = eventDispatcher; 46 | 47 | _rate = 1.0; 48 | _volume = 1.0; 49 | _resizeMode = @"AVLayerVideoGravityResizeAspectFill"; 50 | _pendingSeek = false; 51 | _pendingSeekTime = 0.0f; 52 | _lastSeekTime = 0.0f; 53 | _progressUpdateInterval = 250; 54 | _controls = NO; 55 | _playerBufferEmpty = YES; 56 | 57 | [[NSNotificationCenter defaultCenter] addObserver:self 58 | selector:@selector(applicationWillResignActive:) 59 | name:UIApplicationWillResignActiveNotification 60 | object:nil]; 61 | 62 | [[NSNotificationCenter defaultCenter] addObserver:self 63 | selector:@selector(applicationWillEnterForeground:) 64 | name:UIApplicationWillEnterForegroundNotification 65 | object:nil]; 66 | } 67 | 68 | return self; 69 | } 70 | 71 | - (AVPlayerViewController*)createPlayerViewController:(AVPlayer*)player withPlayerItem:(AVPlayerItem*)playerItem { 72 | AVPlayerViewController* playerLayer= [[AVPlayerViewController alloc] init]; 73 | playerLayer.view.frame = self.bounds; 74 | playerLayer.player = _player; 75 | playerLayer.view.frame = self.bounds; 76 | return playerLayer; 77 | } 78 | 79 | /* --------------------------------------------------------- 80 | ** Get the duration for a AVPlayerItem. 81 | ** ------------------------------------------------------- */ 82 | 83 | - (CMTime)playerItemDuration 84 | { 85 | AVPlayerItem *playerItem = [_player currentItem]; 86 | if (playerItem.status == AVPlayerItemStatusReadyToPlay) 87 | { 88 | return([playerItem duration]); 89 | } 90 | 91 | return(kCMTimeInvalid); 92 | } 93 | 94 | 95 | /* Cancels the previously registered time observer. */ 96 | -(void)removePlayerTimeObserver 97 | { 98 | if (_timeObserver) 99 | { 100 | [_player removeTimeObserver:_timeObserver]; 101 | _timeObserver = nil; 102 | } 103 | } 104 | 105 | #pragma mark - Progress 106 | 107 | - (void)dealloc 108 | { 109 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 110 | } 111 | 112 | #pragma mark - App lifecycle handlers 113 | 114 | - (void)applicationWillResignActive:(NSNotification *)notification 115 | { 116 | if (!_paused) { 117 | [_player pause]; 118 | [_player setRate:0.0]; 119 | } 120 | } 121 | 122 | - (void)applicationWillEnterForeground:(NSNotification *)notification 123 | { 124 | [self applyModifiers]; 125 | } 126 | 127 | #pragma mark - Progress 128 | 129 | - (void)sendProgressUpdate 130 | { 131 | AVPlayerItem *video = [_player currentItem]; 132 | if (video == nil || video.status != AVPlayerItemStatusReadyToPlay) { 133 | return; 134 | } 135 | 136 | CMTime playerDuration = [self playerItemDuration]; 137 | if (CMTIME_IS_INVALID(playerDuration)) { 138 | return; 139 | } 140 | 141 | CMTime currentTime = _player.currentTime; 142 | const Float64 duration = CMTimeGetSeconds(playerDuration); 143 | const Float64 currentTimeSecs = CMTimeGetSeconds(currentTime); 144 | if( currentTimeSecs >= 0 && currentTimeSecs <= duration) { 145 | [_eventDispatcher sendInputEventWithName:@"onVideoProgress" 146 | body:@{ 147 | @"currentTime": [NSNumber numberWithFloat:CMTimeGetSeconds(currentTime)], 148 | @"playableDuration": [self calculatePlayableDuration], 149 | @"atValue": [NSNumber numberWithLongLong:currentTime.value], 150 | @"atTimescale": [NSNumber numberWithInt:currentTime.timescale], 151 | @"target": self.reactTag 152 | }]; 153 | } 154 | } 155 | 156 | /*! 157 | * Calculates and returns the playable duration of the current player item using its loaded time ranges. 158 | * 159 | * \returns The playable duration of the current player item in seconds. 160 | */ 161 | - (NSNumber *)calculatePlayableDuration 162 | { 163 | AVPlayerItem *video = _player.currentItem; 164 | if (video.status == AVPlayerItemStatusReadyToPlay) { 165 | __block CMTimeRange effectiveTimeRange; 166 | [video.loadedTimeRanges enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { 167 | CMTimeRange timeRange = [obj CMTimeRangeValue]; 168 | if (CMTimeRangeContainsTime(timeRange, video.currentTime)) { 169 | effectiveTimeRange = timeRange; 170 | *stop = YES; 171 | } 172 | }]; 173 | Float64 playableDuration = CMTimeGetSeconds(CMTimeRangeGetEnd(effectiveTimeRange)); 174 | if (playableDuration > 0) { 175 | return [NSNumber numberWithFloat:playableDuration]; 176 | } 177 | } 178 | return [NSNumber numberWithInteger:0]; 179 | } 180 | 181 | - (void)addPlayerItemObservers 182 | { 183 | [_playerItem addObserver:self forKeyPath:statusKeyPath options:0 context:nil]; 184 | [_playerItem addObserver:self forKeyPath:playbackBufferEmptyKeyPath options:0 context:nil]; 185 | [_playerItem addObserver:self forKeyPath:playbackLikelyToKeepUpKeyPath options:0 context:nil]; 186 | _playerItemObserversSet = YES; 187 | } 188 | 189 | /* Fixes https://github.com/brentvatne/react-native-video/issues/43 190 | * Crashes caused when trying to remove the observer when there is no 191 | * observer set */ 192 | - (void)removePlayerItemObservers 193 | { 194 | if (_playerItemObserversSet) { 195 | [_playerItem removeObserver:self forKeyPath:statusKeyPath]; 196 | [_playerItem removeObserver:self forKeyPath:playbackBufferEmptyKeyPath]; 197 | [_playerItem removeObserver:self forKeyPath:playbackLikelyToKeepUpKeyPath]; 198 | _playerItemObserversSet = NO; 199 | } 200 | } 201 | 202 | #pragma mark - Player and source 203 | 204 | - (void)setSrc:(NSDictionary *)source 205 | { 206 | [self removePlayerTimeObserver]; 207 | [self removePlayerItemObservers]; 208 | _playerItem = [self playerItemForSource:source]; 209 | [self addPlayerItemObservers]; 210 | 211 | [_player pause]; 212 | [_playerLayer removeFromSuperlayer]; 213 | _playerLayer = nil; 214 | [_playerViewController.view removeFromSuperview]; 215 | _playerViewController = nil; 216 | 217 | _player = [AVPlayer playerWithPlayerItem:_playerItem]; 218 | _player.actionAtItemEnd = AVPlayerActionAtItemEndNone; 219 | 220 | const Float64 progressUpdateIntervalMS = _progressUpdateInterval / 1000; 221 | // @see endScrubbing in AVPlayerDemoPlaybackViewController.m of https://developer.apple.com/library/ios/samplecode/AVPlayerDemo/Introduction/Intro.html 222 | __weak RCTVideo *weakSelf = self; 223 | _timeObserver = [_player addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds(progressUpdateIntervalMS, NSEC_PER_SEC) 224 | queue:NULL 225 | usingBlock:^(CMTime time) { [weakSelf sendProgressUpdate]; } 226 | ]; 227 | [_eventDispatcher sendInputEventWithName:@"onVideoLoadStart" 228 | body:@{@"src": @{ 229 | @"uri": [source objectForKey:@"uri"], 230 | @"type": [source objectForKey:@"type"], 231 | @"isNetwork":[NSNumber numberWithBool:(bool)[source objectForKey:@"isNetwork"]]}, 232 | @"target": self.reactTag}]; 233 | } 234 | 235 | - (AVPlayerItem*)playerItemForSource:(NSDictionary *)source 236 | { 237 | bool isNetwork = [RCTConvert BOOL:[source objectForKey:@"isNetwork"]]; 238 | bool isAsset = [RCTConvert BOOL:[source objectForKey:@"isAsset"]]; 239 | NSString *uri = [source objectForKey:@"uri"]; 240 | NSString *type = [source objectForKey:@"type"]; 241 | 242 | NSURL *url = (isNetwork || isAsset) ? 243 | [NSURL URLWithString:uri] : 244 | [[NSURL alloc] initFileURLWithPath:[[NSBundle mainBundle] pathForResource:uri ofType:type]]; 245 | 246 | if (isAsset) { 247 | AVURLAsset *asset = [AVURLAsset URLAssetWithURL:url options:nil]; 248 | return [AVPlayerItem playerItemWithAsset:asset]; 249 | } 250 | 251 | return [AVPlayerItem playerItemWithURL:url]; 252 | } 253 | 254 | - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context 255 | { 256 | if (object == _playerItem) { 257 | 258 | if ([keyPath isEqualToString:statusKeyPath]) { 259 | // Handle player item status change. 260 | if (_playerItem.status == AVPlayerItemStatusReadyToPlay) { 261 | float duration = CMTimeGetSeconds(_playerItem.asset.duration); 262 | 263 | if (isnan(duration)) { 264 | duration = 0.0; 265 | } 266 | 267 | [_eventDispatcher sendInputEventWithName:@"onVideoLoad" 268 | body:@{@"duration": [NSNumber numberWithFloat:duration], 269 | @"currentTime": [NSNumber numberWithFloat:CMTimeGetSeconds(_playerItem.currentTime)], 270 | @"canPlayReverse": [NSNumber numberWithBool:_playerItem.canPlayReverse], 271 | @"canPlayFastForward": [NSNumber numberWithBool:_playerItem.canPlayFastForward], 272 | @"canPlaySlowForward": [NSNumber numberWithBool:_playerItem.canPlaySlowForward], 273 | @"canPlaySlowReverse": [NSNumber numberWithBool:_playerItem.canPlaySlowReverse], 274 | @"canStepBackward": [NSNumber numberWithBool:_playerItem.canStepBackward], 275 | @"canStepForward": [NSNumber numberWithBool:_playerItem.canStepForward], 276 | @"target": self.reactTag}]; 277 | 278 | [self attachListeners]; 279 | [self applyModifiers]; 280 | } else if(_playerItem.status == AVPlayerItemStatusFailed) { 281 | [_eventDispatcher sendInputEventWithName:@"onVideoError" 282 | body:@{@"error": @{ 283 | @"code": [NSNumber numberWithInteger: _playerItem.error.code], 284 | @"domain": _playerItem.error.domain}, 285 | @"target": self.reactTag}]; 286 | } 287 | } else if ([keyPath isEqualToString:playbackBufferEmptyKeyPath]) { 288 | _playerBufferEmpty = YES; 289 | } else if ([keyPath isEqualToString:playbackLikelyToKeepUpKeyPath]) { 290 | // Continue playing (or not if paused) after being paused due to hitting an unbuffered zone. 291 | if ((!_controls || _playerBufferEmpty) && _playerItem.playbackLikelyToKeepUp) { 292 | [self setPaused:_paused]; 293 | } 294 | _playerBufferEmpty = NO; 295 | } 296 | } else { 297 | [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; 298 | } 299 | } 300 | 301 | - (void)attachListeners 302 | { 303 | // listen for end of file 304 | [[NSNotificationCenter defaultCenter] addObserver:self 305 | selector:@selector(playerItemDidReachEnd:) 306 | name:AVPlayerItemDidPlayToEndTimeNotification 307 | object:[_player currentItem]]; 308 | } 309 | 310 | - (void)playerItemDidReachEnd:(NSNotification *)notification 311 | { 312 | [_eventDispatcher sendInputEventWithName:@"onVideoEnd" body:@{@"target": self.reactTag}]; 313 | 314 | if (_repeat) { 315 | AVPlayerItem *item = [notification object]; 316 | [item seekToTime:kCMTimeZero]; 317 | [self applyModifiers]; 318 | } 319 | } 320 | 321 | #pragma mark - Prop setters 322 | 323 | - (void)setResizeMode:(NSString*)mode 324 | { 325 | if( _controls ) 326 | { 327 | _playerViewController.videoGravity = mode; 328 | } 329 | else 330 | { 331 | _playerLayer.videoGravity = mode; 332 | } 333 | _resizeMode = mode; 334 | } 335 | 336 | - (void)setPaused:(BOOL)paused 337 | { 338 | if (paused) { 339 | [_player pause]; 340 | [_player setRate:0.0]; 341 | } else { 342 | [_player play]; 343 | [_player setRate:_rate]; 344 | } 345 | 346 | _paused = paused; 347 | } 348 | 349 | - (float)getCurrentTime 350 | { 351 | return _playerItem != NULL ? CMTimeGetSeconds(_playerItem.currentTime) : 0; 352 | } 353 | 354 | - (void)setCurrentTime:(float)currentTime 355 | { 356 | [self setSeek: currentTime]; 357 | } 358 | 359 | - (void)setSeek:(float)seekTime 360 | { 361 | int timeScale = 10000; 362 | 363 | AVPlayerItem *item = _player.currentItem; 364 | if (item && item.status == AVPlayerItemStatusReadyToPlay) { 365 | // TODO check loadedTimeRanges 366 | 367 | CMTime cmSeekTime = CMTimeMakeWithSeconds(seekTime, timeScale); 368 | CMTime current = item.currentTime; 369 | // TODO figure out a good tolerance level 370 | CMTime tolerance = CMTimeMake(1000, timeScale); 371 | 372 | if (CMTimeCompare(current, cmSeekTime) != 0) { 373 | [_player seekToTime:cmSeekTime toleranceBefore:tolerance toleranceAfter:tolerance completionHandler:^(BOOL finished) { 374 | [_eventDispatcher sendInputEventWithName:@"onVideoSeek" 375 | body:@{@"currentTime": [NSNumber numberWithFloat:CMTimeGetSeconds(item.currentTime)], 376 | @"seekTime": [NSNumber numberWithFloat:seekTime], 377 | @"target": self.reactTag}]; 378 | }]; 379 | 380 | _pendingSeek = false; 381 | } 382 | 383 | } else { 384 | // TODO: See if this makes sense and if so, actually implement it 385 | _pendingSeek = true; 386 | _pendingSeekTime = seekTime; 387 | } 388 | } 389 | 390 | - (void)setRate:(float)rate 391 | { 392 | _rate = rate; 393 | [self applyModifiers]; 394 | } 395 | 396 | - (void)setMuted:(BOOL)muted 397 | { 398 | _muted = muted; 399 | [self applyModifiers]; 400 | } 401 | 402 | - (void)setVolume:(float)volume 403 | { 404 | _volume = volume; 405 | [self applyModifiers]; 406 | } 407 | 408 | - (void)applyModifiers 409 | { 410 | if (_muted) { 411 | [_player setVolume:0]; 412 | [_player setMuted:YES]; 413 | } else { 414 | [_player setVolume:_volume]; 415 | [_player setMuted:NO]; 416 | } 417 | 418 | [self setResizeMode:_resizeMode]; 419 | [self setRepeat:_repeat]; 420 | [self setPaused:_paused]; 421 | [self setControls:_controls]; 422 | } 423 | 424 | - (void)setRepeat:(BOOL)repeat { 425 | _repeat = repeat; 426 | } 427 | 428 | - (void)usePlayerViewController 429 | { 430 | if( _player ) 431 | { 432 | _playerViewController = [self createPlayerViewController:_player withPlayerItem:_playerItem]; 433 | [self addSubview:_playerViewController.view]; 434 | } 435 | } 436 | 437 | - (void)usePlayerLayer 438 | { 439 | if( _player ) 440 | { 441 | _playerLayer = [AVPlayerLayer playerLayerWithPlayer:_player]; 442 | _playerLayer.frame = self.bounds; 443 | _playerLayer.needsDisplayOnBoundsChange = YES; 444 | 445 | [self.layer addSublayer:_playerLayer]; 446 | self.layer.needsDisplayOnBoundsChange = YES; 447 | } 448 | } 449 | 450 | - (void)setControls:(BOOL)controls 451 | { 452 | if( _controls != controls || (!_playerLayer && !_playerViewController) ) 453 | { 454 | _controls = controls; 455 | if( _controls ) 456 | { 457 | [_playerLayer removeFromSuperlayer]; 458 | _playerLayer = nil; 459 | [self usePlayerViewController]; 460 | } 461 | else 462 | { 463 | [_playerViewController.view removeFromSuperview]; 464 | _playerViewController = nil; 465 | [self usePlayerLayer]; 466 | } 467 | } 468 | } 469 | 470 | #pragma mark - React View Management 471 | 472 | - (void)insertReactSubview:(UIView *)view atIndex:(NSInteger)atIndex 473 | { 474 | // We are early in the game and somebody wants to set a subview. 475 | // That can only be in the context of playerViewController. 476 | if( !_controls && !_playerLayer && !_playerViewController ) 477 | { 478 | [self setControls:true]; 479 | } 480 | 481 | if( _controls ) 482 | { 483 | view.frame = self.bounds; 484 | [_playerViewController.contentOverlayView insertSubview:view atIndex:atIndex]; 485 | } 486 | else 487 | { 488 | RCTLogError(@"video cannot have any subviews"); 489 | } 490 | return; 491 | } 492 | 493 | - (void)removeReactSubview:(UIView *)subview 494 | { 495 | if( _controls ) 496 | { 497 | [subview removeFromSuperview]; 498 | } 499 | else 500 | { 501 | RCTLogError(@"video cannot have any subviews"); 502 | } 503 | return; 504 | } 505 | 506 | - (void)layoutSubviews 507 | { 508 | [super layoutSubviews]; 509 | if( _controls ) 510 | { 511 | _playerViewController.view.frame = self.bounds; 512 | 513 | // also adjust all subviews of contentOverlayView 514 | for (UIView* subview in _playerViewController.contentOverlayView.subviews) { 515 | subview.frame = self.bounds; 516 | } 517 | } 518 | else 519 | { 520 | [CATransaction begin]; 521 | [CATransaction setAnimationDuration:0]; 522 | _playerLayer.frame = self.bounds; 523 | [CATransaction commit]; 524 | } 525 | } 526 | 527 | #pragma mark - Lifecycle 528 | 529 | - (void)removeFromSuperview 530 | { 531 | [_player pause]; 532 | _player = nil; 533 | 534 | [_playerLayer removeFromSuperlayer]; 535 | _playerLayer = nil; 536 | 537 | [_playerViewController.view removeFromSuperview]; 538 | _playerViewController = nil; 539 | 540 | [self removePlayerTimeObserver]; 541 | [self removePlayerItemObservers]; 542 | 543 | _eventDispatcher = nil; 544 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 545 | 546 | [super removeFromSuperview]; 547 | } 548 | 549 | @end 550 | -------------------------------------------------------------------------------- /ios/RCTVideo/RCTVideoManager.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface RCTVideoManager : RCTViewManager 4 | 5 | @end 6 | -------------------------------------------------------------------------------- /ios/RCTVideo/RCTVideoManager.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import "RCTVideoManager.h" 4 | #import "RCTVideo.h" 5 | 6 | @implementation RCTVideoManager 7 | 8 | RCT_EXPORT_MODULE(); 9 | 10 | @synthesize bridge = _bridge; 11 | 12 | - (UIView *)view 13 | { 14 | return [[RCTVideo alloc] initWithEventDispatcher:self.bridge.eventDispatcher]; 15 | } 16 | 17 | /* Should support: onLoadStart, onLoad, and onError to stay consistent with Image */ 18 | 19 | - (NSArray *)customDirectEventTypes 20 | { 21 | return @[ 22 | @"onVideoLoadStart", 23 | @"onVideoLoad", 24 | @"onVideoError", 25 | @"onVideoProgress", 26 | @"onVideoSeek", 27 | @"onVideoEnd" 28 | ]; 29 | } 30 | 31 | - (dispatch_queue_t)methodQueue 32 | { 33 | return dispatch_get_main_queue(); 34 | } 35 | 36 | RCT_EXPORT_VIEW_PROPERTY(src, NSDictionary); 37 | RCT_EXPORT_VIEW_PROPERTY(resizeMode, NSString); 38 | RCT_EXPORT_VIEW_PROPERTY(repeat, BOOL); 39 | RCT_EXPORT_VIEW_PROPERTY(paused, BOOL); 40 | RCT_EXPORT_VIEW_PROPERTY(muted, BOOL); 41 | RCT_EXPORT_VIEW_PROPERTY(controls, BOOL); 42 | RCT_EXPORT_VIEW_PROPERTY(volume, float); 43 | RCT_EXPORT_VIEW_PROPERTY(rate, float); 44 | RCT_EXPORT_VIEW_PROPERTY(seek, float); 45 | RCT_EXPORT_VIEW_PROPERTY(currentTime, float); 46 | 47 | - (NSDictionary *)constantsToExport 48 | { 49 | return @{ 50 | @"ScaleNone": AVLayerVideoGravityResizeAspect, 51 | @"ScaleToFill": AVLayerVideoGravityResize, 52 | @"ScaleAspectFit": AVLayerVideoGravityResizeAspect, 53 | @"ScaleAspectFill": AVLayerVideoGravityResizeAspectFill 54 | }; 55 | } 56 | 57 | @end 58 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@remobile/react-native-video", 3 | "version": "1.0.2", 4 | "description": "A video player for react-native, support hls", 5 | "main": "index.js", 6 | "author": { 7 | "name": "YunJiang.Fang", 8 | "email": "42550564@qq.com" 9 | }, 10 | "license": "MIT", 11 | "keywords": [ 12 | "react-native", 13 | "react-component", 14 | "ios", 15 | "android", 16 | "video", 17 | "hls", 18 | "mp4", 19 | "remobile", 20 | "mobile" 21 | ], 22 | "homepage": "https://github.com/remobile/react-native-video", 23 | "bugs": { 24 | "url": "https://github.com/remobile/react-native-video/issues" 25 | }, 26 | "repository": { 27 | "type": "git", 28 | "url": "git://github.com/remobile/react-native-video.git" 29 | } 30 | } 31 | --------------------------------------------------------------------------------