├── .gitattributes ├── .github ├── ISSUE_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── LICENSE ├── README.md ├── package.json ├── plugin.xml ├── src ├── android │ └── com │ │ └── hutchind │ │ └── cordova │ │ └── plugins │ │ └── streamingmedia │ │ ├── ImageLoadTask.java │ │ ├── SimpleAudioStream.java │ │ ├── SimpleVideoStream.java │ │ └── StreamingMedia.java └── ios │ ├── LandscapeVideo.h │ ├── LandscapeVideo.m │ ├── PortraitVideo.h │ ├── PortraitVideo.m │ ├── StreamingMedia.h │ └── StreamingMedia.m └── www └── StreamingMedia.js /.gitattributes: -------------------------------------------------------------------------------- 1 | # Handle line endings automatically for files detected as text 2 | # and leave all files detected as binary untouched. 3 | text eol=lf 4 | 5 | # 6 | # The above will handle all files NOT found below 7 | # 8 | # These files are text and should be normalized (Convert crlf => lf) 9 | *.css text 10 | *.h text 11 | *.m text 12 | *.df text 13 | *.htm text 14 | *.html text 15 | *.java text 16 | *.js text 17 | *.json text 18 | *.jsp text 19 | *.jspf text 20 | *.jspx text 21 | *.properties text 22 | *.sh text 23 | *.scss text 24 | *.swift text 25 | *.tld text 26 | *.txt text 27 | *.pack text 28 | *.module.ts text 29 | *.tag text 30 | *.tagx text 31 | *.ts text 32 | *.xml text 33 | *.yml text 34 | 35 | # These files are binary and should be left untouched 36 | # (binary is a macro for -text -diff) 37 | *.class binary 38 | *.dll binary 39 | *.ear binary 40 | *.gif binary 41 | *.ico binary 42 | *.jar binary 43 | *.jpg binary 44 | *.jpeg binary 45 | *.png binary 46 | *.so binary 47 | *.war binary 48 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 4 | 5 | **What version of Streaming-Meda-Cordova-Plugin are you using?** 6 | 7 | **What version of Cordova are you using?** 8 | 9 | **What devices are affected?** 10 | 11 | **Please describe the issue in detail, with relevant code samples** 12 | 13 | **What did you expect to happen?** 14 | 15 | **What actually happened?** 16 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 4 | 5 | **What issue is this PR resolving? Alternatively, please describe the bugfix/enhancement this PR aims to provide** 6 | 15 | 16 | **Have you provided unit tests that either prove the bugfix or cover the enhancement?** 17 | 18 | **Related issues** 19 | 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # General 2 | .DS_Store 3 | .AppleDouble 4 | .LSOverride 5 | 6 | # Icon must end with two \r 7 | Icon 8 | 9 | 10 | # Thumbnails 11 | ._* 12 | 13 | # Files that might appear in the root of a volume 14 | .DocumentRevisions-V100 15 | .fseventsd 16 | .Spotlight-V100 17 | .TemporaryItems 18 | .Trashes 19 | .VolumeIcon.icns 20 | .com.apple.timemachine.donotpresent -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2010-2014 Google, Inc. http://angularjs.org 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cordova Streaming Media plugin 2 | 3 | For iOS and Android, by [Nicholas Hutchind](https://github.com/nchutchind) 4 | 5 | ## Description 6 | 7 | This plugin allows you to stream audio and video in a fullscreen, native player on iOS and Android. 8 | 9 | * 1.0.0 Works with Cordova 3.x 10 | * 1.0.1+ Works with Cordova >= 4.0 11 | 12 | ## Message from the maintainer: 13 | 14 | I no longer contribute to Cordova or Ionic full time. If your org needs work on this plugin please consider funding it and hiring me for improvements or otherwise consider donating your time and submitting a PR for whatever you need fixed. My contact info can be found [here](https://github.com/shamilovtim). 15 | 16 | ## Installation 17 | 18 | ``` 19 | cordova plugin add https://github.com/nchutchind/cordova-plugin-streaming-media 20 | ``` 21 | 22 | ### iOS specifics 23 | * Uses the AVPlayerViewController 24 | * Tested on iOS 12 or later 25 | 26 | ### Android specifics 27 | * Uses VideoView and MediaPlayer. 28 | * Creates two activities in your AndroidManifest.xml file. 29 | * Tested on Android 4.0+ 30 | 31 | ## Usage 32 | 33 | ```javascript 34 | var videoUrl = STREAMING_VIDEO_URL; 35 | 36 | // Just play a video 37 | window.plugins.streamingMedia.playVideo(videoUrl); 38 | 39 | // Play a video with callbacks 40 | var options = { 41 | successCallback: function() { 42 | console.log("Video was closed without error."); 43 | }, 44 | errorCallback: function(errMsg) { 45 | console.log("Error! " + errMsg); 46 | }, 47 | orientation: 'landscape', 48 | shouldAutoClose: true, // true(default)/false 49 | controls: true // true(default)/false. Used to hide controls on fullscreen 50 | }; 51 | window.plugins.streamingMedia.playVideo(videoUrl, options); 52 | 53 | 54 | var audioUrl = STREAMING_AUDIO_URL; 55 | 56 | // Play an audio file (not recommended, since the screen will be plain black) 57 | window.plugins.streamingMedia.playAudio(audioUrl); 58 | 59 | // Play an audio file with options (all options optional) 60 | var options = { 61 | bgColor: "#FFFFFF", 62 | bgImage: "", 63 | bgImageScale: "fit", // other valid values: "stretch", "aspectStretch" 64 | initFullscreen: false, // true is default. iOS only. 65 | keepAwake: false, // prevents device from sleeping. true is default. Android only. 66 | successCallback: function() { 67 | console.log("Player closed without error."); 68 | }, 69 | errorCallback: function(errMsg) { 70 | console.log("Error! " + errMsg); 71 | } 72 | }; 73 | window.plugins.streamingMedia.playAudio(audioUrl, options); 74 | 75 | // Stop current audio 76 | window.plugins.streamingMedia.stopAudio(); 77 | 78 | // Pause current audio (iOS only) 79 | window.plugins.streamingMedia.pauseAudio(); 80 | 81 | // Resume current audio (iOS only) 82 | window.plugins.streamingMedia.resumeAudio(); 83 | 84 | ``` 85 | 86 | ## Special Thanks 87 | 88 | [Michael Robinson (@faceleg)](https://github.com/faceleg) 89 | 90 | [Timothy Shamilov (@shamilovtim)](https://github.com/shamilovtim) 91 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cordova-plugin-streaming-media", 3 | "version": "2.3.0", 4 | "description": "This plugin allows you to stream audio and video in a fullscreen, native player on iOS and Android.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/nchutchind/Streaming-Media-Cordova-Plugin.git" 12 | }, 13 | "keywords": [ 14 | "media", 15 | "streaming", 16 | "ecosystem:cordova", 17 | "cordova-ios", 18 | "cordova-android", 19 | "cordova-fireos" 20 | ], 21 | "author": "Nicholas Hutchind", 22 | "contributors": [ 23 | "Timothy Shamilov", 24 | "Michael Robinson" 25 | ], 26 | "license": "MIT", 27 | "bugs": { 28 | "url": "https://github.com/nchutchind/Streaming-Media-Cordova-Plugin/issues" 29 | }, 30 | "homepage": "https://github.com/nchutchind/Streaming-Media-Cordova-Plugin#readme" 31 | } 32 | -------------------------------------------------------------------------------- /plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | StreamingMedia 9 | 10 | 11 | This plugin allows you to launch a fullscreen streaming player for audio or video on iOS and Android. 12 | 13 | 14 | Nicholas Hutchind 15 | 16 | MIT 17 | 18 | Streaming, Media, Video, Audio, Android, iOS 19 | 20 | https://github.com/nchutchind/Streaming-Media-Cordova-Plugin.git 21 | 22 | https://github.com/nchutchind/Streaming-Media-Cordova-Plugin/issues 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | audio 42 | fetch 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /src/android/com/hutchind/cordova/plugins/streamingmedia/ImageLoadTask.java: -------------------------------------------------------------------------------- 1 | package com.hutchind.cordova.plugins.streamingmedia; 2 | 3 | import android.graphics.Bitmap; 4 | import android.graphics.BitmapFactory; 5 | import android.os.AsyncTask; 6 | import android.widget.ImageView; 7 | import android.content.Context; 8 | import java.io.File; 9 | import java.io.InputStream; 10 | import java.net.HttpURLConnection; 11 | import java.net.URL; 12 | import java.io.IOException; 13 | 14 | 15 | public class ImageLoadTask extends AsyncTask { 16 | 17 | private String uri; 18 | private ImageView imageView; 19 | private Context context; 20 | 21 | public ImageLoadTask(String uri, ImageView imageView, Context context) { 22 | this.uri = uri; 23 | this.imageView = imageView; 24 | this.context = context; 25 | } 26 | 27 | @Override 28 | protected Bitmap doInBackground(Void... params) { 29 | Bitmap bitmap = null; 30 | if (uri != null && uri.toLowerCase().startsWith("http")) { 31 | // Load image from URL 32 | try { 33 | URL urlConnection = new URL(uri); 34 | HttpURLConnection connection = (HttpURLConnection) urlConnection.openConnection(); 35 | connection.setDoInput(true); 36 | connection.connect(); 37 | InputStream input = connection.getInputStream(); 38 | bitmap = BitmapFactory.decodeStream(input); 39 | } catch (Exception e) { 40 | e.printStackTrace(); 41 | } 42 | } else { 43 | // Load image from assets 44 | InputStream istr; 45 | try { 46 | istr = context.getAssets().open(uri); 47 | bitmap = BitmapFactory.decodeStream(istr); 48 | } catch (Exception e) { 49 | e.printStackTrace(); 50 | } 51 | } 52 | return bitmap; 53 | } 54 | 55 | @Override 56 | protected void onPostExecute(Bitmap result) { 57 | super.onPostExecute(result); 58 | imageView.setImageBitmap(result); 59 | } 60 | } -------------------------------------------------------------------------------- /src/android/com/hutchind/cordova/plugins/streamingmedia/SimpleAudioStream.java: -------------------------------------------------------------------------------- 1 | package com.hutchind.cordova.plugins.streamingmedia; 2 | 3 | import android.app.Activity; 4 | import android.content.res.Configuration; 5 | import android.graphics.Color; 6 | import android.media.AudioManager; 7 | import android.content.Intent; 8 | import android.media.MediaPlayer; 9 | import android.net.Uri; 10 | import android.os.Bundle; 11 | import android.util.Log; 12 | import android.view.MotionEvent; 13 | import android.widget.ImageView; 14 | import android.view.View; 15 | import android.view.Window; 16 | import android.widget.LinearLayout; 17 | import android.widget.RelativeLayout; 18 | import android.widget.MediaController; 19 | 20 | public class SimpleAudioStream extends Activity implements 21 | MediaPlayer.OnCompletionListener, MediaPlayer.OnPreparedListener, 22 | MediaPlayer.OnErrorListener, MediaPlayer.OnBufferingUpdateListener, 23 | MediaController.MediaPlayerControl { 24 | 25 | private String TAG = getClass().getSimpleName(); 26 | private MediaPlayer mMediaPlayer = null; 27 | private MediaController mMediaController = null; 28 | private LinearLayout mAudioView; 29 | private View mMediaControllerView; 30 | private String mAudioUrl; 31 | private Boolean mShouldAutoClose = true; 32 | 33 | @Override 34 | public void onCreate(Bundle icicle) { 35 | super.onCreate(icicle); 36 | this.requestWindowFeature(Window.FEATURE_NO_TITLE); 37 | Bundle b = getIntent().getExtras(); 38 | mAudioUrl = b.getString("mediaUrl"); 39 | String backgroundColor = b.getString("bgColor"); 40 | String backgroundImagePath = b.getString("bgImage"); 41 | String backgroundImageScale = b.getString("bgImageScale"); 42 | mShouldAutoClose = b.getBoolean("shouldAutoClose", true); 43 | backgroundImageScale = backgroundImageScale == null ? "center" : backgroundImageScale.toLowerCase(); 44 | ImageView.ScaleType bgImageScaleType; 45 | // Default background to black 46 | int bgColor = Color.BLACK; 47 | if (backgroundColor != null) { 48 | bgColor = Color.parseColor(backgroundColor); 49 | } 50 | 51 | if (backgroundImageScale.equals("fit")) { 52 | bgImageScaleType = ImageView.ScaleType.FIT_CENTER; 53 | } else if (backgroundImageScale.equals("stretch")) { 54 | bgImageScaleType = ImageView.ScaleType.FIT_XY; 55 | } else { 56 | bgImageScaleType = ImageView.ScaleType.CENTER; 57 | } 58 | 59 | RelativeLayout audioView = new RelativeLayout(this); 60 | audioView.setBackgroundColor(bgColor); 61 | 62 | if (backgroundImagePath != null) { 63 | ImageView bgImage = new ImageView(this); 64 | new ImageLoadTask(backgroundImagePath, bgImage, getApplicationContext()).execute(null, null); 65 | RelativeLayout.LayoutParams bgImageLayoutParam = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT); 66 | bgImageLayoutParam.addRule(RelativeLayout.CENTER_IN_PARENT); 67 | bgImage.setLayoutParams(bgImageLayoutParam); 68 | bgImage.setScaleType(bgImageScaleType); 69 | audioView.addView(bgImage); 70 | } 71 | 72 | RelativeLayout.LayoutParams relLayoutParam = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT); 73 | mMediaControllerView = new View(this); 74 | audioView.addView(mMediaControllerView); 75 | setContentView(audioView, relLayoutParam); 76 | 77 | 78 | // stop the screen from going to sleep. keepawake parameter from javascript. default is true. 79 | mMediaControllerView.setKeepScreenOn(true); 80 | Boolean keepAwake = b.getBoolean("keepAwake", true); 81 | if (keepAwake == false) { 82 | mMediaControllerView.setKeepScreenOn(false); 83 | } 84 | 85 | play(); 86 | } 87 | 88 | private void play() { 89 | Uri myUri = Uri.parse(mAudioUrl); 90 | try { 91 | if (mMediaPlayer == null) { 92 | mMediaPlayer = new MediaPlayer(); 93 | } else { 94 | try { 95 | mMediaPlayer.stop(); 96 | mMediaPlayer.reset(); 97 | } catch (Exception e) { 98 | Log.e(TAG, e.toString()); 99 | } 100 | } 101 | mMediaPlayer.setDataSource(this, myUri); // Go to Initialized state 102 | mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); 103 | mMediaPlayer.setOnPreparedListener(this); 104 | mMediaPlayer.setOnCompletionListener(this); 105 | mMediaPlayer.setOnBufferingUpdateListener(this); 106 | mMediaPlayer.setOnErrorListener(this); 107 | mMediaPlayer.setScreenOnWhilePlaying(true); 108 | mMediaController = new MediaController(this); 109 | 110 | mMediaPlayer.prepareAsync(); 111 | 112 | Log.d(TAG, "LoadClip Done"); 113 | } catch (Throwable t) { 114 | Log.d(TAG, t.toString()); 115 | } 116 | } 117 | 118 | @Override 119 | public void onPrepared(MediaPlayer mp) { 120 | Log.d(TAG, "Stream is prepared"); 121 | mMediaController.setMediaPlayer(this); 122 | mMediaController.setAnchorView(mMediaControllerView); 123 | mMediaPlayer.start(); 124 | mMediaController.setEnabled(true); 125 | mMediaController.show(); 126 | } 127 | 128 | @Override 129 | public void start() { 130 | if (mMediaPlayer!=null) { 131 | mMediaPlayer.start(); 132 | } 133 | } 134 | 135 | @Override 136 | public void pause() { 137 | if (mMediaPlayer!=null) { 138 | try { 139 | mMediaPlayer.pause(); 140 | } catch (Exception e) { 141 | Log.d(TAG, e.toString()); 142 | } 143 | } 144 | } 145 | 146 | private void stop() { 147 | if (mMediaPlayer!=null) { 148 | try { 149 | mMediaPlayer.stop(); 150 | } catch (Exception e) { 151 | Log.d(TAG, e.toString()); 152 | } 153 | } 154 | } 155 | 156 | public int getDuration() { 157 | return (mMediaPlayer!=null) ? mMediaPlayer.getDuration() : 0; 158 | } 159 | 160 | public int getCurrentPosition() { 161 | return (mMediaPlayer!=null) ? mMediaPlayer.getCurrentPosition() : 0; 162 | } 163 | 164 | public void seekTo(int i) { 165 | if (mMediaPlayer!=null) { 166 | mMediaPlayer.seekTo(i); 167 | } 168 | } 169 | 170 | public boolean isPlaying() { 171 | if (mMediaPlayer!=null) { 172 | try { 173 | return mMediaPlayer.isPlaying(); 174 | } catch (Exception e) { 175 | Log.d(TAG, e.toString()); 176 | } 177 | } 178 | return false; 179 | } 180 | 181 | public int getBufferPercentage() { 182 | return 0; 183 | } 184 | 185 | public boolean canPause() { 186 | return true; 187 | } 188 | 189 | public boolean canSeekBackward() { 190 | return true; 191 | } 192 | 193 | public boolean canSeekForward() { 194 | return true; 195 | } 196 | 197 | @Override 198 | public int getAudioSessionId() { 199 | return 0; 200 | } 201 | 202 | @Override 203 | public void onDestroy() { 204 | super.onDestroy(); 205 | if (mMediaPlayer!=null){ 206 | try { 207 | mMediaPlayer.reset(); 208 | mMediaPlayer.release(); 209 | } catch (Exception e) { 210 | Log.e(TAG, e.toString()); 211 | } 212 | mMediaPlayer = null; 213 | } 214 | } 215 | 216 | private void wrapItUp(int resultCode, String message) { 217 | Intent intent = new Intent(); 218 | intent.putExtra("message", message); 219 | setResult(resultCode, intent); 220 | finish(); 221 | } 222 | 223 | 224 | @Override 225 | public void onCompletion(MediaPlayer mp) { 226 | stop(); 227 | if (mShouldAutoClose) { 228 | Log.v(TAG, "FINISHING ACTIVITY"); 229 | wrapItUp(RESULT_OK, null); 230 | } 231 | 232 | } 233 | 234 | public boolean onError(MediaPlayer mp, int what, int extra) { 235 | StringBuilder sb = new StringBuilder(); 236 | sb.append("Media Player Error: "); 237 | switch (what) { 238 | case MediaPlayer.MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK: 239 | sb.append("Not Valid for Progressive Playback"); 240 | break; 241 | case MediaPlayer.MEDIA_ERROR_SERVER_DIED: 242 | sb.append("Server Died"); 243 | break; 244 | case MediaPlayer.MEDIA_ERROR_UNKNOWN: 245 | sb.append("Unknown"); 246 | break; 247 | default: 248 | sb.append(" Non standard ("); 249 | sb.append(what); 250 | sb.append(")"); 251 | } 252 | sb.append(" (" + what + ") "); 253 | sb.append(extra); 254 | Log.e(TAG, sb.toString()); 255 | wrapItUp(RESULT_CANCELED, sb.toString()); 256 | return true; 257 | } 258 | 259 | public void onBufferingUpdate(MediaPlayer mp, int percent) { 260 | Log.d(TAG, "PlayerService onBufferingUpdate : " + percent + "%"); 261 | } 262 | 263 | @Override 264 | public void onBackPressed() { 265 | wrapItUp(RESULT_OK, null); 266 | } 267 | 268 | @Override 269 | public void onConfigurationChanged(Configuration newConfig) { 270 | super.onConfigurationChanged(newConfig); 271 | } 272 | 273 | @Override 274 | public boolean onTouchEvent(MotionEvent event) { 275 | if (mMediaController != null) { 276 | mMediaController.show(); 277 | } 278 | return false; 279 | } 280 | } -------------------------------------------------------------------------------- /src/android/com/hutchind/cordova/plugins/streamingmedia/SimpleVideoStream.java: -------------------------------------------------------------------------------- 1 | package com.hutchind.cordova.plugins.streamingmedia; 2 | 3 | import android.app.Activity; 4 | import android.content.res.Configuration; 5 | import android.graphics.Color; 6 | import android.graphics.Point; 7 | import android.media.MediaPlayer; 8 | import android.widget.MediaController; 9 | import android.content.Intent; 10 | import android.content.pm.ActivityInfo; 11 | import android.view.MotionEvent; 12 | import android.net.Uri; 13 | import android.os.Bundle; 14 | import android.util.Log; 15 | import android.view.Display; 16 | import android.view.View; 17 | import android.view.Window; 18 | import android.view.WindowManager; 19 | import android.widget.MediaController; 20 | import android.widget.ProgressBar; 21 | import android.widget.RelativeLayout; 22 | import android.widget.VideoView; 23 | 24 | public class SimpleVideoStream extends Activity implements 25 | MediaPlayer.OnCompletionListener, MediaPlayer.OnPreparedListener, 26 | MediaPlayer.OnErrorListener, MediaPlayer.OnBufferingUpdateListener { 27 | private String TAG = getClass().getSimpleName(); 28 | private VideoView mVideoView = null; 29 | private MediaPlayer mMediaPlayer = null; 30 | private MediaController mMediaController = null; 31 | private ProgressBar mProgressBar = null; 32 | private String mVideoUrl; 33 | private Boolean mShouldAutoClose = true; 34 | private boolean mControls; 35 | 36 | @Override 37 | protected void onCreate(Bundle savedInstanceState) { 38 | super.onCreate(savedInstanceState); 39 | this.requestWindowFeature(Window.FEATURE_NO_TITLE); 40 | this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); 41 | 42 | Bundle b = getIntent().getExtras(); 43 | mVideoUrl = b.getString("mediaUrl"); 44 | mShouldAutoClose = b.getBoolean("shouldAutoClose", true); 45 | mControls = b.getBoolean("controls", true); 46 | 47 | RelativeLayout relLayout = new RelativeLayout(this); 48 | relLayout.setBackgroundColor(Color.BLACK); 49 | RelativeLayout.LayoutParams relLayoutParam = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT); 50 | relLayoutParam.addRule(RelativeLayout.CENTER_IN_PARENT, RelativeLayout.TRUE); 51 | mVideoView = new VideoView(this); 52 | mVideoView.setLayoutParams(relLayoutParam); 53 | relLayout.addView(mVideoView); 54 | 55 | // Create progress throbber 56 | mProgressBar = new ProgressBar(this); 57 | mProgressBar.setIndeterminate(true); 58 | // Center the progress bar 59 | RelativeLayout.LayoutParams pblp = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT); 60 | pblp.addRule(RelativeLayout.CENTER_IN_PARENT, RelativeLayout.TRUE); 61 | mProgressBar.setLayoutParams(pblp); 62 | // Add progress throbber to view 63 | relLayout.addView(mProgressBar); 64 | mProgressBar.bringToFront(); 65 | 66 | setOrientation(b.getString("orientation")); 67 | 68 | setContentView(relLayout, relLayoutParam); 69 | 70 | play(); 71 | } 72 | 73 | private void play() { 74 | mProgressBar.setVisibility(View.VISIBLE); 75 | Uri videoUri = Uri.parse(mVideoUrl); 76 | try { 77 | mVideoView.setOnCompletionListener(this); 78 | mVideoView.setOnPreparedListener(this); 79 | mVideoView.setOnErrorListener(this); 80 | mVideoView.setVideoURI(videoUri); 81 | mMediaController = new MediaController(this); 82 | mMediaController.setAnchorView(mVideoView); 83 | mMediaController.setMediaPlayer(mVideoView); 84 | if (!mControls) { 85 | mMediaController.setVisibility(View.GONE); 86 | } 87 | mVideoView.setMediaController(mMediaController); 88 | } catch (Throwable t) { 89 | Log.d(TAG, t.toString()); 90 | } 91 | } 92 | 93 | private void setOrientation(String orientation) { 94 | if ("landscape".equals(orientation)) { 95 | this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); 96 | }else if("portrait".equals(orientation)) { 97 | this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); 98 | } 99 | } 100 | 101 | private Runnable checkIfPlaying = new Runnable() { 102 | @Override 103 | public void run() { 104 | if (mVideoView.getCurrentPosition() > 0) { 105 | // Video is not at the very beginning anymore. 106 | // Hide the progress bar. 107 | mProgressBar.setVisibility(View.GONE); 108 | } else { 109 | // Video is still at the very beginning. 110 | // Check again after a small amount of time. 111 | mVideoView.postDelayed(checkIfPlaying, 100); 112 | } 113 | } 114 | }; 115 | 116 | @Override 117 | public void onPrepared(MediaPlayer mp) { 118 | Log.d(TAG, "Stream is prepared"); 119 | mMediaPlayer = mp; 120 | mMediaPlayer.setOnBufferingUpdateListener(this); 121 | mVideoView.requestFocus(); 122 | mVideoView.start(); 123 | mVideoView.postDelayed(checkIfPlaying, 0); 124 | } 125 | 126 | private void pause() { 127 | Log.d(TAG, "Pausing video."); 128 | mVideoView.pause(); 129 | } 130 | 131 | private void stop() { 132 | Log.d(TAG, "Stopping video."); 133 | mVideoView.stopPlayback(); 134 | } 135 | 136 | @Override 137 | public void onDestroy() { 138 | super.onDestroy(); 139 | Log.d(TAG, "onDestroy triggered."); 140 | stop(); 141 | } 142 | 143 | private void wrapItUp(int resultCode, String message) { 144 | Log.d(TAG, "wrapItUp was triggered."); 145 | Intent intent = new Intent(); 146 | intent.putExtra("message", message); 147 | setResult(resultCode, intent); 148 | finish(); 149 | } 150 | 151 | public void onCompletion(MediaPlayer mp) { 152 | Log.d(TAG, "onCompletion triggered."); 153 | stop(); 154 | if (mShouldAutoClose) { 155 | wrapItUp(RESULT_OK, null); 156 | } 157 | } 158 | 159 | public boolean onError(MediaPlayer mp, int what, int extra) { 160 | StringBuilder sb = new StringBuilder(); 161 | sb.append("MediaPlayer Error: "); 162 | switch (what) { 163 | case MediaPlayer.MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK: 164 | sb.append("Not Valid for Progressive Playback"); 165 | break; 166 | case MediaPlayer.MEDIA_ERROR_SERVER_DIED: 167 | sb.append("Server Died"); 168 | break; 169 | case MediaPlayer.MEDIA_ERROR_UNKNOWN: 170 | sb.append("Unknown"); 171 | break; 172 | default: 173 | sb.append(" Non standard ("); 174 | sb.append(what); 175 | sb.append(")"); 176 | } 177 | sb.append(" (" + what + ") "); 178 | sb.append(extra); 179 | Log.e(TAG, sb.toString()); 180 | 181 | wrapItUp(RESULT_CANCELED, sb.toString()); 182 | return true; 183 | } 184 | 185 | public void onBufferingUpdate(MediaPlayer mp, int percent) { 186 | Log.d(TAG, "onBufferingUpdate : " + percent + "%"); 187 | } 188 | 189 | @Override 190 | public void onBackPressed() { 191 | // If we're leaving, let's finish the activity 192 | wrapItUp(RESULT_OK, null); 193 | } 194 | 195 | @Override 196 | public void onConfigurationChanged(Configuration newConfig) { 197 | // The screen size changed or the orientation changed... don't restart the activity 198 | super.onConfigurationChanged(newConfig); 199 | } 200 | 201 | @Override 202 | public boolean onTouchEvent(MotionEvent event) { 203 | if (mMediaController != null) 204 | mMediaController.show(); 205 | return false; 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /src/android/com/hutchind/cordova/plugins/streamingmedia/StreamingMedia.java: -------------------------------------------------------------------------------- 1 | package com.hutchind.cordova.plugins.streamingmedia; 2 | 3 | import android.app.Activity; 4 | import android.graphics.Color; 5 | import android.os.Bundle; 6 | import android.util.Log; 7 | import android.content.ContentResolver; 8 | import android.content.Intent; 9 | import android.os.Build; 10 | import java.util.Iterator; 11 | import org.json.JSONArray; 12 | import org.json.JSONObject; 13 | import org.json.JSONException; 14 | import org.apache.cordova.CallbackContext; 15 | import org.apache.cordova.CordovaInterface; 16 | import org.apache.cordova.CordovaPlugin; 17 | import org.apache.cordova.PluginResult; 18 | 19 | public class StreamingMedia extends CordovaPlugin { 20 | public static final String ACTION_PLAY_AUDIO = "playAudio"; 21 | public static final String ACTION_PLAY_VIDEO = "playVideo"; 22 | 23 | private static final int ACTIVITY_CODE_PLAY_MEDIA = 7; 24 | 25 | private CallbackContext callbackContext; 26 | 27 | private static final String TAG = "StreamingMediaPlugin"; 28 | 29 | @Override 30 | public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException { 31 | this.callbackContext = callbackContext; 32 | JSONObject options = null; 33 | 34 | try { 35 | options = args.getJSONObject(1); 36 | } catch (JSONException e) { 37 | // Developer provided no options. Leave options null. 38 | } 39 | 40 | if (ACTION_PLAY_AUDIO.equals(action)) { 41 | return playAudio(args.getString(0), options); 42 | } else if (ACTION_PLAY_VIDEO.equals(action)) { 43 | return playVideo(args.getString(0), options); 44 | } else { 45 | callbackContext.error("streamingMedia." + action + " is not a supported method."); 46 | return false; 47 | } 48 | } 49 | 50 | private boolean playAudio(String url, JSONObject options) { 51 | return play(SimpleAudioStream.class, url, options); 52 | } 53 | private boolean playVideo(String url, JSONObject options) { 54 | return play(SimpleVideoStream.class, url, options); 55 | } 56 | 57 | private boolean play(final Class activityClass, final String url, final JSONObject options) { 58 | final CordovaInterface cordovaObj = cordova; 59 | final CordovaPlugin plugin = this; 60 | 61 | cordova.getActivity().runOnUiThread(new Runnable() { 62 | public void run() { 63 | final Intent streamIntent = new Intent(cordovaObj.getActivity().getApplicationContext(), activityClass); 64 | Bundle extras = new Bundle(); 65 | extras.putString("mediaUrl", url); 66 | 67 | if (options != null) { 68 | Iterator optKeys = options.keys(); 69 | while (optKeys.hasNext()) { 70 | try { 71 | final String optKey = (String)optKeys.next(); 72 | if (options.get(optKey).getClass().equals(String.class)) { 73 | extras.putString(optKey, (String)options.get(optKey)); 74 | Log.v(TAG, "Added option: " + optKey + " -> " + String.valueOf(options.get(optKey))); 75 | } else if (options.get(optKey).getClass().equals(Boolean.class)) { 76 | extras.putBoolean(optKey, (Boolean)options.get(optKey)); 77 | Log.v(TAG, "Added option: " + optKey + " -> " + String.valueOf(options.get(optKey))); 78 | } 79 | 80 | } catch (JSONException e) { 81 | Log.e(TAG, "JSONException while trying to read options. Skipping option."); 82 | } 83 | } 84 | streamIntent.putExtras(extras); 85 | } 86 | 87 | cordovaObj.startActivityForResult(plugin, streamIntent, ACTIVITY_CODE_PLAY_MEDIA); 88 | } 89 | }); 90 | return true; 91 | } 92 | 93 | public void onActivityResult(int requestCode, int resultCode, Intent intent) { 94 | Log.v(TAG, "onActivityResult: " + requestCode + " " + resultCode); 95 | super.onActivityResult(requestCode, resultCode, intent); 96 | if (ACTIVITY_CODE_PLAY_MEDIA == requestCode) { 97 | if (Activity.RESULT_OK == resultCode) { 98 | this.callbackContext.success(); 99 | } else if (Activity.RESULT_CANCELED == resultCode) { 100 | String errMsg = "Error"; 101 | if (intent != null && intent.hasExtra("message")) { 102 | errMsg = intent.getStringExtra("message"); 103 | } 104 | this.callbackContext.error(errMsg); 105 | } 106 | } 107 | } 108 | } -------------------------------------------------------------------------------- /src/ios/LandscapeVideo.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @interface LandscapeAVPlayerViewController: AVPlayerViewController 5 | { 6 | 7 | } 8 | @end 9 | -------------------------------------------------------------------------------- /src/ios/LandscapeVideo.m: -------------------------------------------------------------------------------- 1 | #import "LandscapeVideo.h" 2 | 3 | @implementation LandscapeAVPlayerViewController 4 | 5 | - (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation { 6 | return UIInterfaceOrientationLandscapeRight; // or LandscapeLeft 7 | } 8 | 9 | - (UIInterfaceOrientationMask)supportedInterfaceOrientations { 10 | return UIInterfaceOrientationMaskLandscape; 11 | } 12 | @end 13 | -------------------------------------------------------------------------------- /src/ios/PortraitVideo.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @interface PortraitAVPlayerViewController: AVPlayerViewController 5 | { 6 | 7 | } 8 | @end 9 | -------------------------------------------------------------------------------- /src/ios/PortraitVideo.m: -------------------------------------------------------------------------------- 1 | #import "PortraitVideo.h" 2 | 3 | @implementation PortraitAVPlayerViewController 4 | 5 | - (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation { 6 | return UIInterfaceOrientationPortrait; // or PortraitUpsideDown 7 | } 8 | 9 | - (UIInterfaceOrientationMask)supportedInterfaceOrientations { 10 | return UIInterfaceOrientationMaskPortrait; // or PortraitUpsideDown 11 | } 12 | @end 13 | -------------------------------------------------------------------------------- /src/ios/StreamingMedia.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import 4 | #import 5 | 6 | @interface StreamingMedia : CDVPlugin 7 | @property (nonatomic, strong) AVAudioSession* avSession; 8 | 9 | - (void)playVideo:(CDVInvokedUrlCommand*)command; 10 | - (void)playAudio:(CDVInvokedUrlCommand*)command; 11 | 12 | @end 13 | -------------------------------------------------------------------------------- /src/ios/StreamingMedia.m: -------------------------------------------------------------------------------- 1 | #import "StreamingMedia.h" 2 | #import 3 | #import 4 | #import 5 | #import "LandscapeVideo.h" 6 | #import "PortraitVideo.h" 7 | 8 | @interface StreamingMedia() 9 | - (void)parseOptions:(NSDictionary *) options type:(NSString *) type; 10 | - (void)play:(CDVInvokedUrlCommand *) command type:(NSString *) type; 11 | - (void)setBackgroundColor:(NSString *)color; 12 | - (void)setImage:(NSString*)imagePath withScaleType:(NSString*)imageScaleType; 13 | - (UIImage*)getImage: (NSString *)imageName; 14 | - (void)startPlayer:(NSString*)uri; 15 | - (void)moviePlayBackDidFinish:(NSNotification*)notification; 16 | - (void)cleanup; 17 | @end 18 | 19 | @implementation StreamingMedia { 20 | NSString* callbackId; 21 | AVPlayerViewController *moviePlayer; 22 | BOOL shouldAutoClose; 23 | UIColor *backgroundColor; 24 | UIImageView *imageView; 25 | BOOL initFullscreen; 26 | NSString *mOrientation; 27 | NSString *videoType; 28 | AVPlayer *movie; 29 | BOOL controls; 30 | } 31 | 32 | NSString * const TYPE_VIDEO = @"VIDEO"; 33 | NSString * const TYPE_AUDIO = @"AUDIO"; 34 | NSString * const DEFAULT_IMAGE_SCALE = @"center"; 35 | 36 | -(void)parseOptions:(NSDictionary *)options type:(NSString *) type { 37 | // Common options 38 | mOrientation = options[@"orientation"] ?: @"default"; 39 | 40 | if (![options isKindOfClass:[NSNull class]] && [options objectForKey:@"shouldAutoClose"]) { 41 | shouldAutoClose = [[options objectForKey:@"shouldAutoClose"] boolValue]; 42 | } else { 43 | shouldAutoClose = YES; 44 | } 45 | if (![options isKindOfClass:[NSNull class]] && [options objectForKey:@"bgColor"]) { 46 | [self setBackgroundColor:[options objectForKey:@"bgColor"]]; 47 | } else { 48 | backgroundColor = [UIColor blackColor]; 49 | } 50 | 51 | if (![options isKindOfClass:[NSNull class]] && [options objectForKey:@"initFullscreen"]) { 52 | initFullscreen = [[options objectForKey:@"initFullscreen"] boolValue]; 53 | } else { 54 | initFullscreen = YES; 55 | } 56 | 57 | if (![options isKindOfClass:[NSNull class]] && [options objectForKey:@"controls"]) { 58 | controls = [[options objectForKey:@"controls"] boolValue]; 59 | } else { 60 | controls = YES; 61 | } 62 | 63 | if ([type isEqualToString:TYPE_AUDIO]) { 64 | videoType = TYPE_AUDIO; 65 | 66 | // bgImage 67 | // bgImageScale 68 | if (![options isKindOfClass:[NSNull class]] && [options objectForKey:@"bgImage"]) { 69 | NSString *imageScale = DEFAULT_IMAGE_SCALE; 70 | if (![options isKindOfClass:[NSNull class]] && [options objectForKey:@"bgImageScale"]) { 71 | imageScale = [options objectForKey:@"bgImageScale"]; 72 | } 73 | [self setImage:[options objectForKey:@"bgImage"] withScaleType:imageScale]; 74 | } 75 | // bgColor 76 | if (![options isKindOfClass:[NSNull class]] && [options objectForKey:@"bgColor"]) { 77 | NSLog(@"Found option for bgColor"); 78 | [self setBackgroundColor:[options objectForKey:@"bgColor"]]; 79 | } else { 80 | backgroundColor = [UIColor blackColor]; 81 | } 82 | } else { 83 | // Reset overlay on video player after playing audio 84 | [self cleanup]; 85 | } 86 | // No specific options for video yet 87 | } 88 | 89 | -(void)play:(CDVInvokedUrlCommand *) command type:(NSString *) type { 90 | NSLog(@"play called"); 91 | callbackId = command.callbackId; 92 | NSString *mediaUrl = [command.arguments objectAtIndex:0]; 93 | [self parseOptions:[command.arguments objectAtIndex:1] type:type]; 94 | 95 | [self startPlayer:mediaUrl]; 96 | } 97 | 98 | -(void)stop:(CDVInvokedUrlCommand *) command type:(NSString *) type { 99 | NSLog(@"stop called"); 100 | callbackId = command.callbackId; 101 | if (moviePlayer.player) { 102 | [moviePlayer.player pause]; 103 | } 104 | } 105 | 106 | -(void)playVideo:(CDVInvokedUrlCommand *) command { 107 | NSLog(@"playvideo called"); 108 | [self ignoreMute]; 109 | [self play:command type:[NSString stringWithString:TYPE_VIDEO]]; 110 | } 111 | 112 | -(void)playAudio:(CDVInvokedUrlCommand *) command { 113 | NSLog(@"playaudio called"); 114 | [self ignoreMute]; 115 | [self play:command type:[NSString stringWithString:TYPE_AUDIO]]; 116 | } 117 | 118 | -(void)stopAudio:(CDVInvokedUrlCommand *) command { 119 | [self stop:command type:[NSString stringWithString:TYPE_AUDIO]]; 120 | } 121 | 122 | // Ignore the mute button 123 | -(void)ignoreMute { 124 | AVAudioSession *session = [AVAudioSession sharedInstance]; 125 | [session setCategory:AVAudioSessionCategoryPlayback error:nil]; 126 | } 127 | 128 | -(void) setBackgroundColor:(NSString *)color { 129 | NSLog(@"setbackgroundcolor called"); 130 | if ([color hasPrefix:@"#"]) { 131 | // HEX value 132 | unsigned rgbValue = 0; 133 | NSScanner *scanner = [NSScanner scannerWithString:color]; 134 | [scanner setScanLocation:1]; // bypass '#' character 135 | [scanner scanHexInt:&rgbValue]; 136 | backgroundColor = [UIColor colorWithRed:((float)((rgbValue & 0xFF0000) >> 16))/255.0 green:((float)((rgbValue & 0xFF00) >> 8))/255.0 blue:((float)(rgbValue & 0xFF))/255.0 alpha:1.0]; 137 | } else { 138 | // Color name 139 | NSString *selectorString = [[color lowercaseString] stringByAppendingString:@"Color"]; 140 | SEL selector = NSSelectorFromString(selectorString); 141 | UIColor *colorObj = [UIColor blackColor]; 142 | if ([UIColor respondsToSelector:selector]) { 143 | colorObj = [UIColor performSelector:selector]; 144 | } 145 | backgroundColor = colorObj; 146 | } 147 | } 148 | 149 | -(UIImage*)getImage: (NSString *)imageName { 150 | NSLog(@"getimage called"); 151 | UIImage *image = nil; 152 | if (imageName != (id)[NSNull null]) { 153 | if ([imageName hasPrefix:@"http"]) { 154 | // Web image 155 | image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:imageName]]]; 156 | } else if ([imageName hasPrefix:@"www/"]) { 157 | // Asset image 158 | image = [UIImage imageNamed:imageName]; 159 | } else if ([imageName hasPrefix:@"file://"]) { 160 | // Stored image 161 | image = [UIImage imageWithData:[NSData dataWithContentsOfFile:[[NSURL URLWithString:imageName] path]]]; 162 | } else if ([imageName hasPrefix:@"data:"]) { 163 | // base64 encoded string 164 | NSURL *imageURL = [NSURL URLWithString:imageName]; 165 | NSData *imageData = [NSData dataWithContentsOfURL:imageURL]; 166 | image = [UIImage imageWithData:imageData]; 167 | } else { 168 | // explicit path 169 | image = [UIImage imageWithData:[NSData dataWithContentsOfFile:imageName]]; 170 | } 171 | } 172 | return image; 173 | } 174 | 175 | - (void)orientationChanged:(NSNotification *)notification { 176 | NSLog(@"orientationchanged called"); 177 | if (imageView != nil) { 178 | // adjust imageView for rotation 179 | imageView.bounds = moviePlayer.contentOverlayView.bounds; 180 | imageView.frame = moviePlayer.contentOverlayView.frame; 181 | } 182 | } 183 | 184 | -(void)setImage:(NSString*)imagePath withScaleType:(NSString*)imageScaleType { 185 | NSLog(@"setimage called"); 186 | imageView = [[UIImageView alloc] initWithFrame:self.viewController.view.bounds]; 187 | 188 | if (imageScaleType == nil) { 189 | NSLog(@"imagescaletype was NIL"); 190 | imageScaleType = DEFAULT_IMAGE_SCALE; 191 | } 192 | 193 | if ([imageScaleType isEqualToString:@"stretch"]){ 194 | // Stretches image to fill all available background space, disregarding aspect ratio 195 | imageView.contentMode = UIViewContentModeScaleToFill; 196 | } else if ([imageScaleType isEqualToString:@"fit"]) { 197 | // fits entire image perfectly 198 | imageView.contentMode = UIViewContentModeScaleAspectFit; 199 | } else if ([imageScaleType isEqualToString:@"aspectStretch"]) { 200 | // Stretches image to fill all possible space while retaining aspect ratio 201 | imageView.contentMode = UIViewContentModeScaleAspectFill; 202 | } else { 203 | // Places image in the center of the screen 204 | imageView.contentMode = UIViewContentModeCenter; 205 | //moviePlayer.backgroundView.contentMode = UIViewContentModeCenter; 206 | } 207 | 208 | [imageView setImage:[self getImage:imagePath]]; 209 | } 210 | 211 | -(void)startPlayer:(NSString*)uri { 212 | NSLog(@"startplayer called"); 213 | NSURL *url = [NSURL URLWithString:uri]; 214 | movie = [AVPlayer playerWithURL:url]; 215 | 216 | // handle orientation 217 | [self handleOrientation]; 218 | 219 | // handle gestures 220 | [self handleGestures]; 221 | 222 | [moviePlayer setPlayer:movie]; 223 | [moviePlayer setShowsPlaybackControls:controls]; 224 | [moviePlayer setUpdatesNowPlayingInfoCenter:YES]; 225 | 226 | if(@available(iOS 11.0, *)) { [moviePlayer setEntersFullScreenWhenPlaybackBegins:YES]; } 227 | 228 | // present modally so we get a close button 229 | [self.viewController presentViewController:moviePlayer animated:YES completion:^(void){ 230 | [moviePlayer.player play]; 231 | }]; 232 | 233 | // add audio image and background color 234 | if ([videoType isEqualToString:TYPE_AUDIO]) { 235 | if (imageView != nil) { 236 | [moviePlayer.contentOverlayView setAutoresizesSubviews:YES]; 237 | [moviePlayer.contentOverlayView addSubview:imageView]; 238 | } 239 | moviePlayer.contentOverlayView.backgroundColor = backgroundColor; 240 | [self.viewController.view addSubview:moviePlayer.view]; 241 | } 242 | 243 | // setup listners 244 | [self handleListeners]; 245 | } 246 | 247 | - (void) handleListeners { 248 | 249 | // Listen for re-maximize 250 | [[NSNotificationCenter defaultCenter] addObserver:self 251 | selector:@selector(appDidBecomeActive:) 252 | name:UIApplicationDidBecomeActiveNotification 253 | object:nil]; 254 | 255 | // Listen for minimize 256 | [[NSNotificationCenter defaultCenter] addObserver:self 257 | selector:@selector(appDidEnterBackground:) 258 | name:UIApplicationDidEnterBackgroundNotification 259 | object:nil]; 260 | 261 | // Listen for playback finishing 262 | [[NSNotificationCenter defaultCenter] addObserver:self 263 | selector:@selector(moviePlayBackDidFinish:) 264 | name:AVPlayerItemDidPlayToEndTimeNotification 265 | object:moviePlayer.player.currentItem]; 266 | 267 | // Listen for errors 268 | [[NSNotificationCenter defaultCenter] addObserver:self 269 | selector:@selector(moviePlayBackDidFinish:) 270 | name:AVPlayerItemFailedToPlayToEndTimeNotification 271 | object:moviePlayer.player.currentItem]; 272 | 273 | // Listen for orientation change 274 | [[NSNotificationCenter defaultCenter] addObserver:self 275 | selector:@selector(orientationChanged:) 276 | name:UIDeviceOrientationDidChangeNotification 277 | object:nil]; 278 | 279 | /* Listen for click on the "Done" button 280 | 281 | // Deprecated.. AVPlayerController doesn't offer a "Done" listener... thanks apple. We'll listen for an error when playback finishes 282 | [[NSNotificationCenter defaultCenter] addObserver:self 283 | selector:@selector(doneButtonClick:) 284 | name:MPMoviePlayerWillExitFullscreenNotification 285 | object:nil]; 286 | */ 287 | } 288 | 289 | - (void) handleGestures { 290 | // Get buried nested view 291 | UIView *contentView = [moviePlayer.view valueForKey:@"contentView"]; 292 | 293 | // loop through gestures, remove swipes 294 | for (UIGestureRecognizer *recognizer in contentView.gestureRecognizers) { 295 | NSLog(@"gesture loop "); 296 | NSLog(@"%@", recognizer); 297 | if ([recognizer isKindOfClass:[UIPanGestureRecognizer class]]) { 298 | [contentView removeGestureRecognizer:recognizer]; 299 | } 300 | if ([recognizer isKindOfClass:[UIPinchGestureRecognizer class]]) { 301 | [contentView removeGestureRecognizer:recognizer]; 302 | } 303 | if ([recognizer isKindOfClass:[UIRotationGestureRecognizer class]]) { 304 | [contentView removeGestureRecognizer:recognizer]; 305 | } 306 | if ([recognizer isKindOfClass:[UILongPressGestureRecognizer class]]) { 307 | [contentView removeGestureRecognizer:recognizer]; 308 | } 309 | if ([recognizer isKindOfClass:[UIScreenEdgePanGestureRecognizer class]]) { 310 | [contentView removeGestureRecognizer:recognizer]; 311 | } 312 | if ([recognizer isKindOfClass:[UISwipeGestureRecognizer class]]) { 313 | [contentView removeGestureRecognizer:recognizer]; 314 | } 315 | } 316 | } 317 | 318 | - (void) handleOrientation { 319 | // hnadle the subclassing of the view based on the orientation variable 320 | if ([mOrientation isEqualToString:@"landscape"]) { 321 | moviePlayer = [[LandscapeAVPlayerViewController alloc] init]; 322 | } else if ([mOrientation isEqualToString:@"portrait"]) { 323 | moviePlayer = [[PortraitAVPlayerViewController alloc] init]; 324 | } else { 325 | moviePlayer = [[AVPlayerViewController alloc] init]; 326 | } 327 | } 328 | 329 | - (void) appDidEnterBackground:(NSNotification*)notification { 330 | NSLog(@"appDidEnterBackground"); 331 | 332 | if (moviePlayer && movie && videoType == TYPE_AUDIO) 333 | { 334 | NSLog(@"did set player layer to nil"); 335 | [moviePlayer setPlayer: nil]; 336 | } 337 | } 338 | 339 | - (void) appDidBecomeActive:(NSNotification*)notification { 340 | NSLog(@"appDidBecomeActive"); 341 | 342 | if (moviePlayer && movie && videoType == TYPE_AUDIO) 343 | { 344 | NSLog(@"did reinstate playerlayer"); 345 | [moviePlayer setPlayer:movie]; 346 | } 347 | } 348 | 349 | - (void) moviePlayBackDidFinish:(NSNotification*)notification { 350 | NSLog(@"Playback did finish with auto close being %d, and error message being %@", shouldAutoClose, notification.userInfo); 351 | NSDictionary *notificationUserInfo = [notification userInfo]; 352 | NSNumber *errorValue = [notificationUserInfo objectForKey:AVPlayerItemFailedToPlayToEndTimeErrorKey]; 353 | NSString *errorMsg; 354 | if (errorValue) { 355 | NSError *mediaPlayerError = [notificationUserInfo objectForKey:@"error"]; 356 | if (mediaPlayerError) { 357 | errorMsg = [mediaPlayerError localizedDescription]; 358 | } else { 359 | errorMsg = @"Unknown error."; 360 | } 361 | NSLog(@"Playback failed: %@", errorMsg); 362 | } 363 | 364 | if (shouldAutoClose || [errorMsg length] != 0) { 365 | [self cleanup]; 366 | CDVPluginResult* pluginResult; 367 | if ([errorMsg length] != 0) { 368 | pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:errorMsg]; 369 | } else { 370 | pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsBool:true]; 371 | } 372 | [self.commandDelegate sendPluginResult:pluginResult callbackId:callbackId]; 373 | } 374 | } 375 | 376 | 377 | - (void)cleanup { 378 | NSLog(@"Clean up called"); 379 | imageView = nil; 380 | initFullscreen = false; 381 | backgroundColor = nil; 382 | 383 | // Remove playback finished listener 384 | [[NSNotificationCenter defaultCenter] 385 | removeObserver:self 386 | name:AVPlayerItemDidPlayToEndTimeNotification 387 | object:moviePlayer.player.currentItem]; 388 | // Remove playback finished error listener 389 | [[NSNotificationCenter defaultCenter] 390 | removeObserver:self 391 | name:AVPlayerItemFailedToPlayToEndTimeNotification 392 | object:moviePlayer.player.currentItem]; 393 | // Remove orientation change listener 394 | [[NSNotificationCenter defaultCenter] 395 | removeObserver:self 396 | name:UIDeviceOrientationDidChangeNotification 397 | object:nil]; 398 | 399 | if (moviePlayer) { 400 | [moviePlayer.player pause]; 401 | [moviePlayer dismissViewControllerAnimated:YES completion:nil]; 402 | moviePlayer = nil; 403 | } 404 | } 405 | @end 406 | -------------------------------------------------------------------------------- /www/StreamingMedia.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | function StreamingMedia() { 3 | } 4 | 5 | StreamingMedia.prototype.playAudio = function (url, options) { 6 | options = options || {}; 7 | cordova.exec(options.successCallback || null, options.errorCallback || null, "StreamingMedia", "playAudio", [url, options]); 8 | }; 9 | 10 | StreamingMedia.prototype.pauseAudio = function (options) { 11 | options = options || {}; 12 | cordova.exec(options.successCallback || null, options.errorCallback || null, "StreamingMedia", "pauseAudio", [options]); 13 | }; 14 | 15 | StreamingMedia.prototype.resumeAudio = function (options) { 16 | options = options || {}; 17 | cordova.exec(options.successCallback || null, options.errorCallback || null, "StreamingMedia", "resumeAudio", [options]); 18 | }; 19 | 20 | StreamingMedia.prototype.stopAudio = function (options) { 21 | options = options || {}; 22 | cordova.exec(options.successCallback || null, options.errorCallback || null, "StreamingMedia", "stopAudio", [options]); 23 | }; 24 | 25 | StreamingMedia.prototype.playVideo = function (url, options) { 26 | options = options || {}; 27 | cordova.exec(options.successCallback || null, options.errorCallback || null, "StreamingMedia", "playVideo", [url, options]); 28 | }; 29 | 30 | 31 | StreamingMedia.install = function () { 32 | if (!window.plugins) { 33 | window.plugins = {}; 34 | } 35 | window.plugins.streamingMedia = new StreamingMedia(); 36 | return window.plugins.streamingMedia; 37 | }; 38 | 39 | cordova.addConstructor(StreamingMedia.install); --------------------------------------------------------------------------------