visit
//gzht888.com/how-to-implement-video-ads-in-react-native-apps
expo run:android
In app/build.gradle:
defaultConfig {
...
//add below
multiDexEnabled true
}
...
dependencies {
..
//add
implementation 'androidx.multidex:multidex:2.0.1'
implementation 'androidx.appcompat:appcompat:1.3.1'
implementation 'com.google.android.exoplayer:exoplayer-core:2.15.1'
implementation 'com.google.android.exoplayer:exoplayer-ui:2.15.1'
implementation 'com.google.android.exoplayer:extension-ima:2.15.1'
implementation 'com.google.android.exoplayer:exoplayer-hls:2.15.1'
}
In app/src/main/AndroidManifest.xml:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="//schemas.android.com/apk/res/android"
package="com.sidelineaccess.bhs">
<!-- Required permissions for the IMA SDK -->
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
...
<service android:name=".AudioPlayerService"/>
</application>
</manifest>
Add to app/src/main/res/values/strings.xml (for playing audio)
<string name="notification_channel">channel</string>
<string name="notification_channel_description">Audio Broadcast</string>
Add a layout file for the Video View: app/src/main/res/layout/video_view.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="//schemas.android.com/apk/res/android"
android:id="@+id/connections"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#000000" >
<com.google.android.exoplayer2.ui.PlayerView
android:id="@+id/player_view"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
Add a layout file for the Audio Player: app/src/main/res/layout/audio_view.xml
This will be just a small line for the Audio Player, but as we are implementing Native UI Components, this will be the counterpart to the React Native View.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="//schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#DDDDDD">
</LinearLayout>
Add app/src/main/java/com/mascotmedia/Media/ReactNativePackage.java (new Java file)
In this file, we are returning the VideoViewManager and the AudioViewManager.
package com.mascotmedia.Media;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class ReactNativePackage implements ReactPackage {
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Arrays.<ViewManager>asList(
//to be created
new VideoViewManager(reactContext),
new AudioViewManager(reactContext)
);
}
}
In app/src/main/java/com/mascotmedia/Media/MainApplication.java
add the new Package to the returned packages.
@Override
protected List<ReactPackage> getPackages() {
@SuppressWarnings("UnnecessaryLocalVariable")
List<ReactPackage> packages = new PackageList(this).getPackages();
// Packages that cannot be autolinked yet can be added manually here
//add line below
packages.add(new ReactNativePackage());
return packages;
}
Add the app/src/main/java/com/mascotmedia/Media/VideoViewManager.java (new Java file)
This ViewManager will be returning the view for React Native.
package com.mascotmedia.Media;
import android.view.Choreographer;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import androidx.fragment.app.Fragment;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.FragmentActivity;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.common.MapBuilder;
import com.facebook.react.uimanager.annotations.ReactProp;
import com.facebook.react.uimanager.annotations.ReactPropGroup;
import com.facebook.react.uimanager.ViewGroupManager;
import com.facebook.react.uimanager.ThemedReactContext;
import java.util.Map;
public class VideoViewManager extends ViewGroupManager<FrameLayout> {
public static final String REACT_CLASS = "VideoViewManager";
public final int COMMAND_CREATE = 1;
public final int COMMAND_KILL = 2;
private String videoUrl;
private String adTagUrl;
private VideoFragment localVideoFragment;
ReactApplicationContext reactContext;
public VideoViewManager(ReactApplicationContext reactContext) {
this.reactContext = reactContext;
}
@Override
public String getName() {
return REACT_CLASS;
}
/**
* Return a FrameLayout which will later hold the Fragment
*/
@Override
public FrameLayout createViewInstance(ThemedReactContext reactContext) {
return new FrameLayout(reactContext);
}
/**
* Map the "create" command to an integer
*/
@Nullable
@Override
public Map<String, Integer> getCommandsMap() {
return MapBuilder.of("create", COMMAND_CREATE,"kill", COMMAND_KILL);
}
/**
* Handle "create" command (called from JS) and call createFragment method
*/
@Override
public void receiveCommand(@NonNull FrameLayout root, String commandId, @Nullable ReadableArray args) {
super.receiveCommand(root, commandId, args);
int reactNativeViewId = args.getInt(0);
int commandIdInt = Integer.parseInt(commandId);
switch (commandIdInt) {
case COMMAND_CREATE:
createFragment(root, reactNativeViewId);
break;
case COMMAND_KILL:
removeFragment(root, reactNativeViewId);
break;
default: {}
}
}
@ReactProp(name = "videoUrl")
public void setVideoUrl(FrameLayout view, String value) {
videoUrl = value;
}
@ReactProp(name = "adTagUrl")
public void setAdTagUrl(FrameLayout view, String value) {
adTagUrl = value;
}
/**
* Replace your React Native view with a custom fragment
*/
public void createFragment(FrameLayout root, int reactNativeViewId) {
ViewGroup parentView = (ViewGroup) root.findViewById(reactNativeViewId).getParent();
setupLayout((ViewGroup) parentView);
final VideoFragment videoFragment = new VideoFragment();
videoFragment.setVideoUrl(videoUrl);
videoFragment.setAdTagUrl(adTagUrl);
videoFragment.setReactNativeViewId(reactNativeViewId);
videoFragment.setReactContext(reactContext);
localVideoFragment = videoFragment;
FragmentActivity activity = (FragmentActivity) reactContext.getCurrentActivity();
activity.getSupportFragmentManager()
.beginTransaction()
.replace(reactNativeViewId, videoFragment, String.valueOf(reactNativeViewId))
.commit();
}
/**
* Replace your React Native view with a custom fragment
*/
public void removeFragment(FrameLayout root, int reactNativeViewId) {
FragmentActivity activity = (FragmentActivity) reactContext.getCurrentActivity();
VideoFragment videoFragment = (VideoFragment) activity.getSupportFragmentManager().findFragmentById(reactNativeViewId);
if(videoFragment != null) {
activity.getSupportFragmentManager()
.beginTransaction().
remove(videoFragment).commit();
}
}
public void setupLayout(ViewGroup view) {
Choreographer.getInstance().postFrameCallback(new Choreographer.FrameCallback() {
@Override
public void doFrame(long frameTimeNanos) {
manuallyLayoutChildren(view);
view.getViewTreeObserver().dispatchOnGlobalLayout();
Choreographer.getInstance().postFrameCallback(this);
}
});
}
/**
* Layout all children properly
*/
public void manuallyLayoutChildren(ViewGroup view) {
for (int i=0; i < view.getChildCount(); i++){
View child = view.getChildAt(i);
child.measure(
View.MeasureSpec.makeMeasureSpec(view.getMeasuredWidth(),
View.MeasureSpec.EXACTLY),
View.MeasureSpec.makeMeasureSpec(view.getMeasuredHeight(),
View.MeasureSpec.EXACTLY));
child.layout(0, 0, child.getMeasuredWidth(), child.getMeasuredHeight());
}
}
@Nullable
@Override
public Map getExportedCustomDirectEventTypeConstants() {
return super.getExportedCustomDirectEventTypeConstants();
}
@Override
public void onDropViewInstance(FrameLayout view) {
super.onDropViewInstance(view);
if(localVideoFragment!=null) localVideoFragment.onDestroy();
}
}
Add app/src/main/java/com/mascotmedia/Media/VideoFragment.java (new Java file)
The Fragment will get the parameters from the VideoViewManager and initialize the VideoPlayer to play the ads and the video content.
package com.mascotmedia.Media;
import android.app.PendingIntent;
import android.content.Intent;
import android.graphics.Bitmap;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import android.net.Uri;
import android.os.Bundle;
import androidx.multidex.MultiDex;
import android.app.Service;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.events.RCTEventEmitter;
import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.ExoPlayer;//
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.ext.ima.ImaAdsLoader;
import com.google.android.exoplayer2.RenderersFactory;
import com.google.android.exoplayer2.source.DefaultMediaSourceFactory;
import com.google.android.exoplayer2.source.hls.DefaultHlsExtractorFactory;
import com.google.android.exoplayer2.source.hls.HlsExtractorFactory;
import com.google.android.exoplayer2.source.MediaSourceFactory;
import com.google.android.exoplayer2.source.ads.AdsLoader;//
import com.google.android.exoplayer2.ui.PlayerNotificationManager;
import com.google.android.exoplayer2.ui.PlayerView;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
import com.google.android.exoplayer2.util.Util;
public class VideoFragment extends Fragment {
private PlayerView playerView;
private SimpleExoPlayer player;
private ImaAdsLoader adsLoader;
private String videoUrl;
private String adTagUrl;
private int reactNativeViewId;
private ReactApplicationContext reactContext;
public void setVideoUrl(String videoUrl) {
this.videoUrl = videoUrl;
}
public void setAdTagUrl(String adTagUrl) {
this.adTagUrl = adTagUrl;
}
public void setReactNativeViewId(int reactNativeViewId) {
this.reactNativeViewId = reactNativeViewId;
}
public void setReactContext(ReactApplicationContext reactContext) {
this.reactContext = reactContext;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
return inflater.inflate(R.layout.video_view,parent,false);
}
private void releasePlayer() {
adsLoader.setPlayer(null);
playerView.setPlayer(null);
if(player!=null) player.release();
player = null;
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
// do any logic that should happen in an `onCreate` method
MultiDex.install(requireContext());
playerView = (PlayerView) view.findViewById(R.id.player_view);
// Create an AdsLoader.
adsLoader = new ImaAdsLoader.Builder(requireContext()).build();
}
private void initializePlayer() {
// Set up the factory for media sources, passing the ads loader and ad view providers.
DataSource.Factory dataSourceFactory =
new DefaultDataSourceFactory(requireContext(), Util.getUserAgent(requireContext(), getString(R.string.app_name)));
MediaSourceFactory mediaSourceFactory =
new DefaultMediaSourceFactory(dataSourceFactory)
//new DefaultHlsExtractorFactory(dataSourceFactory)
.setAdsLoaderProvider(unusedAdTagUri -> adsLoader)
.setAdViewProvider(playerView);
// Create a SimpleExoPlayer and set it as the player for content and ads.
player = new SimpleExoPlayer.Builder(requireContext()).setMediaSourceFactory(mediaSourceFactory).build();
playerView.setPlayer(player);
adsLoader.setPlayer(player);
MediaItem mediaItem;
// Create the MediaItem to play, specifying the content URI and ad tag URI.
//check for valid link in RN!
Uri contentUri = Uri.parse(videoUrl);
Uri adTagUri = null;
if (adTagUrl != null && !adTagUrl.equals("")) {
adTagUri = Uri.parse(adTagUrl);
mediaItem = new MediaItem.Builder().setUri(contentUri).setAdTagUri(adTagUri).build();
} else{
mediaItem = new MediaItem.Builder().setUri(contentUri).build();
}
// Prepare the content and ad to be played with the SimpleExoPlayer.
player.setMediaItem(mediaItem);
player.prepare();
// Set PlayWhenReady. If true, content and ads will autoplay.
player.setPlayWhenReady(true);
}
@Override
public void onPause() {
super.onPause();
if (playerView != null) {
playerView.onPause();
}
releasePlayer();
}
@Override
public void onResume() {
super.onResume();
initializePlayer();
if (playerView != null) {
playerView.onResume();
}
}
@Override
public void onDestroy() {
super.onDestroy();
if (playerView != null) {
playerView.onPause();
}
releasePlayer();
adsLoader.release();
}
}
Addapp/src/main/java/com/macotmedia/Media/AudioVideoViewManager.java (new Java file).
package com.mascotmedia.Media;
import android.view.Choreographer;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.FragmentActivity;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.common.MapBuilder;
import com.facebook.react.uimanager.annotations.ReactProp;
import com.facebook.react.uimanager.annotations.ReactPropGroup;
import com.facebook.react.uimanager.ViewGroupManager;
import com.facebook.react.uimanager.ThemedReactContext;
import java.util.Map;
public class AudioViewManager extends ViewGroupManager<FrameLayout> {
public static final String REACT_CLASS = "AudioViewManager";
public final int COMMAND_CREATE = 1;
public final int COMMAND_KILL = 2;
public final int COMMAND_RESUME = 3;
public final int COMMAND_PAUSE = 4;
private String audioUrl;
private AudioFragment localAudioFragment;
ReactApplicationContext reactContext;
public AudioViewManager(ReactApplicationContext reactContext) {
this.reactContext = reactContext;
}
@Override
public String getName() {
return REACT_CLASS;
}
/**
* Return a FrameLayout which will later hold the Fragment
*/
@Override
public FrameLayout createViewInstance(ThemedReactContext reactContext) {
return new FrameLayout(reactContext);
}
/**
* Map the "create" command to an integer
*/
@Nullable
@Override
public Map<String, Integer> getCommandsMap() {
return MapBuilder.of("create", COMMAND_CREATE,"kill", COMMAND_KILL, "resume", COMMAND_RESUME, "pause", COMMAND_PAUSE);
}
/**
* Handle "create" command (called from JS) and call createFragment method
*/
@Override
public void receiveCommand(@NonNull FrameLayout root, String commandId, @Nullable ReadableArray args) {
super.receiveCommand(root, commandId, args);
int reactNativeViewId = args.getInt(0);
int commandIdInt = Integer.parseInt(commandId);
System.out.println("****AudioViewManager: receiveCommand: " + commandIdInt);
switch (commandIdInt) {
case COMMAND_CREATE:
createFragment(root, reactNativeViewId);
break;
case COMMAND_KILL:
removeFragment(root, reactNativeViewId);
break;
case COMMAND_RESUME:
resumeAudio(root, reactNativeViewId);
break;
case COMMAND_PAUSE:
pauseAudio(root, reactNativeViewId);
break;
default: {}
}
}
@ReactProp(name = "audioUrl")
public void setAudioUrl(FrameLayout view, String value) {
audioUrl = value;
System.out.println("****AudioViewManager: setAudioUrl: " + value);
}
/**
* Replace your React Native view with a custom fragment
*/
public void createFragment(FrameLayout root, int reactNativeViewId) {
ViewGroup parentView = (ViewGroup) root.findViewById(reactNativeViewId).getParent();
setupLayout((ViewGroup) parentView);
final AudioFragment audioFragment = new AudioFragment();
audioFragment.setAudioUrl(audioUrl);
audioFragment.setReactNativeViewId(reactNativeViewId);
audioFragment.setReactContext(reactContext);
localAudioFragment = audioFragment;
FragmentActivity activity = (FragmentActivity) reactContext.getCurrentActivity();
activity.getSupportFragmentManager()
.beginTransaction()
.replace(reactNativeViewId, audioFragment, String.valueOf(reactNativeViewId))
.commit();
}
/**
* Stop Playing and Remove the Fragment
*/
public void removeFragment(FrameLayout root, int reactNativeViewId) {
//System.out.println("****AudioViewManager: remove Fragment: " + reactNativeViewId);
FragmentActivity activity = (FragmentActivity) reactContext.getCurrentActivity();
AudioFragment audioFragment = (AudioFragment) activity.getSupportFragmentManager().findFragmentById(reactNativeViewId);
if(audioFragment != null) {
//System.out.println("****AudioViewManager: remove Fragment - found fragment");
activity.getSupportFragmentManager()
.beginTransaction().
remove(audioFragment).commit();
}
}
/**
* Pause Playing
*/
public void pauseAudio(FrameLayout root, int reactNativeViewId) {
System.out.println("****AudioViewManager: pause Audio: " + reactNativeViewId);
FragmentActivity activity = (FragmentActivity) reactContext.getCurrentActivity();
AudioFragment audioFragment = (AudioFragment) activity.getSupportFragmentManager().findFragmentById(reactNativeViewId);
if(audioFragment != null) {
audioFragment.pausePlaying();
}
}
/**
* Resume Playing
*/
public void resumeAudio(FrameLayout root, int reactNativeViewId) {
System.out.println("****AudioViewManager: resume Audio: " + reactNativeViewId);
FragmentActivity activity = (FragmentActivity) reactContext.getCurrentActivity();
AudioFragment audioFragment = (AudioFragment) activity.getSupportFragmentManager().findFragmentById(reactNativeViewId);
if(audioFragment != null) {
audioFragment.resumePlaying();
}
}
public void setupLayout(ViewGroup view) {
Choreographer.getInstance().postFrameCallback(new Choreographer.FrameCallback() {
@Override
public void doFrame(long frameTimeNanos) {
manuallyLayoutChildren(view);
view.getViewTreeObserver().dispatchOnGlobalLayout();
Choreographer.getInstance().postFrameCallback(this);
}
});
}
/**
* Layout all children properly
*/
public void manuallyLayoutChildren(ViewGroup view) {
for (int i=0; i < view.getChildCount(); i++){
View child = view.getChildAt(i);
child.measure(
View.MeasureSpec.makeMeasureSpec(view.getMeasuredWidth(),
View.MeasureSpec.EXACTLY),
View.MeasureSpec.makeMeasureSpec(view.getMeasuredHeight(),
View.MeasureSpec.EXACTLY));
child.layout(0, 0, child.getMeasuredWidth(), child.getMeasuredHeight());
}
}
@Nullable
@Override
public Map getExportedCustomDirectEventTypeConstants() {
return super.getExportedCustomDirectEventTypeConstants();
}
@Override
public void onDropViewInstance(FrameLayout view) {
super.onDropViewInstance(view);
if(localAudioFragment!=null) localAudioFragment.onDestroy();
localAudioFragment = null;
}
}
Add app/src/main/java/com/mascotmedia/Media/AudioFragment.java (new Java file)
The AudioFragment will initialize, start and stop the AudioPlayerService.
package com.mascotmedia.Media;
import android.app.PendingIntent;
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.content.ServiceConnection;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import android.net.Uri;
import android.os.Bundle;
import androidx.multidex.MultiDex;
import android.app.Service;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.events.RCTEventEmitter;
import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.ExoPlayer;//
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.RenderersFactory;
import com.google.android.exoplayer2.source.DefaultMediaSourceFactory;
import com.google.android.exoplayer2.source.MediaSourceFactory;
import com.google.android.exoplayer2.ui.PlayerNotificationManager;
import com.google.android.exoplayer2.ui.PlayerView;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
import com.google.android.exoplayer2.util.Util;
public class AudioFragment extends Fragment {
private SimpleExoPlayer player;
private PlayerNotificationManager playerNotificationManager;
private String audioUrl;
private int reactNativeViewId;
private ReactApplicationContext reactContext;
boolean bounded;
private AudioPlayerService audioPlayerService;
public void setAudioUrl(String audioUrl) {
this.audioUrl = audioUrl;
}
public void setReactNativeViewId(int reactNativeViewId) {
this.reactNativeViewId = reactNativeViewId;
}
public void setReactContext(ReactApplicationContext reactContext) {
this.reactContext = reactContext;
}
public void resumePlaying(){
audioPlayerService.resume();
}
public void pausePlaying(){
audioPlayerService.pause();
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
return inflater.inflate(R.layout.audio_view,parent,false);
}
ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
audioPlayerService = ((AudioPlayerService.AudioServiceBinder)service).getService();
}
@Override
public void onServiceDisconnected(ComponentName name) {
bounded = false;
audioPlayerService = null;
}
};
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
// do any logic that should happen in an `onCreate` method
MultiDex.install(requireContext());
Intent intent = new Intent(requireContext(), AudioPlayerService.class);
intent.putExtra("audioUrl",audioUrl);
if(requireContext().bindService(intent, mConnection, Context.BIND_AUTO_CREATE)){
bounded = true;
} else{
System.out.println("****AudioFragment: could not bind service");
}
Util.startForegroundService(requireContext(),intent);
}
@Override
public void onPause() {
super.onPause();
// do any logic that should happen in an `onPause` method
// e.g.: customView.onPause();
}
@Override
public void onResume() {
super.onResume();
// do any logic that should happen in an `onResume` method
// e.g.: customView.onResume();
}
@Override
public void onDestroy() {
// do any logic that should happen in an `onDestroy` method
if(audioPlayerService != null) {
audioPlayerService.pause();
audioPlayerService.stopForeground(true);
audioPlayerService.stopSelf();
}
super.onDestroy();
}
}
Addapp/src/main/java/com/mascotmedia/Media/AudioPlayerService.java (new Java file).
package com.mascotmedia.Media;
import android.app.Notification;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Binder;
import android.os.IBinder;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.source.DefaultMediaSourceFactory;
import com.google.android.exoplayer2.source.MediaSourceFactory;
import com.google.android.exoplayer2.ui.PlayerNotificationManager;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
import com.google.android.exoplayer2.util.Util;
public class AudioPlayerService extends Service {
private SimpleExoPlayer player;
private PlayerNotificationManager playerNotificationManager;
private String audioUrl;
public void setAudioUrl(String audioUrl) {
this.audioUrl = audioUrl;
}
public class AudioServiceBinder extends Binder {
AudioPlayerService getService() {
return AudioPlayerService.this;
}
}
@Override
public void onCreate(){
super.onCreate();
final Context context = this;
player = new SimpleExoPlayer.Builder(context).build();
playerNotificationManager = new PlayerNotificationManager.Builder(this, 1, "channel").setMediaDescriptionAdapter(new PlayerNotificationManager.MediaDescriptionAdapter() {
@Override
public CharSequence getCurrentContentTitle(Player player) {
return "Audio Broadcast";
}
@Nullable
@Override
public PendingIntent createCurrentContentIntent(Player player) {
Intent intent = new Intent(context, MainActivity.class);
return PendingIntent.getActivity(context,0,intent, PendingIntent.FLAG_UPDATE_CURRENT);
}
@Nullable
@Override
public CharSequence getCurrentContentText(Player player) {
return "";
}
@Nullable
@Override
public Bitmap getCurrentLargeIcon(Player player, PlayerNotificationManager.BitmapCallback callback) {
Bitmap mIcon = BitmapFactory.decodeResource(context.getResources(),R.drawable.notification_icon);
return mIcon;
}
}).setNotificationListener(new PlayerNotificationManager.NotificationListener() {
@Override
public void onNotificationCancelled(int notificationId, boolean dismissedByUser) {
stopSelf();
}
@Override
public void onNotificationPosted(int notificationId, Notification notification, boolean ongoing) {
startForeground(notificationId,notification);
}
})
.setChannelNameResourceId(R.string.notification_channel)
.setChannelDescriptionResourceId(R.string.notification_channel_description)
.build();
playerNotificationManager.setPlayer(player);
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
private final IBinder mBinder = new AudioServiceBinder();
@Override
public void onDestroy() {
stopForeground(true);
playerNotificationManager.setPlayer(null);
if(player!=null) {
player.setPlayWhenReady(false);
player.release();
}
player = null;
super.onDestroy();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
setAudioUrl(intent.getStringExtra("audioUrl"));
// Set the media item to be played.
player.setMediaItem(MediaItem.fromUri(audioUrl));
// Prepare the player.
player.prepare();
player.setPlayWhenReady(true);
return START_STICKY;
}
public void pause(){
player.setPlayWhenReady(false);
}
public void resume(){
player.setPlayWhenReady(true);
}
}
<Stack.Screen name="Audio" component={AudioScreen} />
<Button
title="Audio"
onPress={() => navigation.navigate('Audio')}
/>
import React, { useState, useRef } from "react";
import {
View,
Dimensions,
TouchableOpacity,
Image,
} from "react-native";
import AudioViewIos from "../components/AudioViewIos";
import AudioViewAndroid from "../components/AudioViewAndroid";
import Constants from "expo-constants";
const audioLink = "//www.largesound.com/ashborytour/sound/AshboryBYU.mp3";
const width=Dimensions.get("window").width;
export default function AudioStreamModal() {
const ref = useRef(null);
const [playingAudio, setPlayingAudio] = useState(true);
async function toggleAudio() {
//we are playing right now, pause
if (playingAudio) {
if (ref.current) {
ref.current.pauseAudio();
setPlayingAudio(false);
}
//we are paused, play
} else {
if (ref.current) {
ref.current.resumeAudio();
setPlayingAudio(true);
}
}
}
return (
<View style={{ flex: 1, alignItems: "center", justifyContent: "center", paddingBottom:80 }}>
<TouchableOpacity onPress={toggleAudio}>
{playingAudio && (
<Image
style={{ width: 100, height: 100, alignSelf: "center" }}
resizeMode="cover"
source={require("../assets/video_pause_transparent.png")}
/>
)}
{!playingAudio && (
<Image
style={{ width: 100, height: 100, alignSelf: "center" }}
resizeMode="cover"
source={require("../assets/video_play_transparent.png")}
/>
)}
</TouchableOpacity>
{Constants.platform.android && (
<View
style={{
height: 3,
width: width * 0.64,
backgroundColor: "grey",
}}
>
<AudioViewAndroid ref={ref} url={audioLink} />
</View>
)}
{Constants.platform.ios && (
<View
style={{
height: 3,
width: width * 0.64,
backgroundColor: "grey",
}}
>
<AudioViewIos
ref={ref}
url={audioLink}
/>
</View>
)}
</View>
);
}
Here is the React Native Audio View for Android (AudioViewAndroid.js):
import React, {
useEffect,
useRef,
useImperativeHandle,
forwardRef,
} from "react";
import {
UIManager,
findNodeHandle,
} from "react-native";
import { requireNativeComponent } from "react-native";
const AudioViewManager = requireNativeComponent("AudioViewManager");
function AudioView(props, ref) {
const nativeRef = useRef(null);
useImperativeHandle(ref, () => ({
// methods connected to `ref`
resumeAudio: () => {
if (__DEV__) {
console.log("RN calling resumeAudio ");
}
resumeAudio();
},
pauseAudio: () => {
if (__DEV__) {
console.log("RN calling pauseAudio ");
}
pauseAudio();
}
}));
function createFragment() {
const viewId = findNodeHandle(nativeRef.current);
if (viewId != null) {
UIManager.dispatchViewManagerCommand(
viewId,
UIManager.AudioViewManager.Commands.create.toString(), // we are calling the 'create' command
[viewId]
);
}
}
function killFragment() {
const viewId = findNodeHandle(nativeRef.current);
if (viewId != null) {
UIManager.dispatchViewManagerCommand(
viewId,
UIManager.AudioViewManager.Commands.kill.toString(), // we are calling the 'kill' command
[viewId]
);
}
}
function resumeAudio() {
if (__DEV__) {
console.log("RN resumeAudio ");
}
const viewId = findNodeHandle(nativeRef.current);
if (viewId != null) {
UIManager.dispatchViewManagerCommand(
viewId,
UIManager.AudioViewManager.Commands.resume.toString(), // we are calling the 'resume' command
[viewId]
);
}
}
function pauseAudio() {
if (__DEV__) {
console.log("RN pauseAudio ");
}
const viewId = findNodeHandle(nativeRef.current);
if (viewId != null) {
UIManager.dispatchViewManagerCommand(
viewId,
UIManager.AudioViewManager.Commands.pause.toString(), // we are calling the 'pause' command
[viewId]
);
}
}
useEffect(() => {
if (__DEV__) {
console.log("RN AudioView: " + props.url);
}
setTimeout(() => {
createFragment();
}, 500);
return () => {
//kill the android side when leaving the view
killFragment();
};
}, []);
return (
<AudioViewManager
ref={nativeRef}
audioUrl={props.url}
/>
);
}
export default forwardRef(AudioView);
import * as React from "react";
import { View, Text, Dimensions } from "react-native";
import Constants from "expo-constants";
import VideoViewIos from "../components/VideoViewIos";
import VideoViewAndroid from "../components/VideoViewAndroid";
const videoLink =
"//devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/master.m3u8";
const adTagUrl =
"//pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/single_ad_samples&ciu_szs=300x250&impl=s&gdfp_req=1&env=vp&output=vast&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ct%3Dlinear&correlator=";
const width = Dimensions.get("window").width;
export default function VideoScreen() {
return (
<View
style={{
flex: 1,
alignItems: "center",
justifyContent: "center",
paddingBottom: 80,
}}
>
{Constants.platform.ios && (
<VideoViewIos
videoUrl={videoLink}
adTagUrl={adTagUrl}
style={{
height: width * 0.56,
width: width,
backgroundColor: "grey",
}}
/>
)}
{Constants.platform.android && (
<View
style={{
height: width * 0.56,
width: width,
backgroundColor: "grey",
}}
>
<VideoViewAndroid
url={videoLink}
adTag={adTagUrl}
/>
</View>
)}
</View>
);
}
import React, { useEffect, useRef } from "react";
import { UIManager, findNodeHandle } from "react-native";
import { requireNativeComponent } from "react-native";
const VideoViewManager = requireNativeComponent("VideoViewManager");
const VideoView = ({ url, adTag }) => {
const nativeRef = useRef(null);
useEffect(() => {
if (__DEV__) {
console.log("RN VideoView: " + url);
}
setTimeout(() => {
createFragment();
}, 500);
return () => {
//kill the android side when leaving the view
killFragment();
};
}, []);
function createFragment() {
const viewId = findNodeHandle(nativeRef.current);
if (viewId != null) {
UIManager.dispatchViewManagerCommand(
viewId,
UIManager.VideoViewManager.Commands.create.toString(), // we are calling the 'create' command
[viewId]
);
}
}
function killFragment() {
const viewId = findNodeHandle(nativeRef.current);
if (viewId != null) {
UIManager.dispatchViewManagerCommand(
viewId,
UIManager.VideoViewManager.Commands.kill.toString(), // we are calling the 'kill' command
[viewId]
);
}
}
return (
<VideoViewManager
ref={nativeRef}
videoUrl={url}
adTagUrl={adTag}
/>
);
};
export default VideoView;
To check out the full implementation for iOS and Android, see the repository on Github.
Expo
Using Native Components in React Native
React Navigation
Google IMA