Merge pull request #3211 from mysport12/master

Feature: audioOutput - Play over phone earpiece
This commit is contained in:
Olivier Bouillet 2023-08-31 08:25:12 +02:00 committed by GitHub
commit 0bf7f70e24
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 93 additions and 1 deletions

8
API.md
View File

@ -294,6 +294,7 @@ var styles = StyleSheet.create({
| [adTagUrl](#adTagUrl) | Android, iOS |
| [allowsExternalPlayback](#allowsexternalplayback) | iOS |
| [audioOnly](#audioonly) | All |
| [audioOutput](#audioOutput) | Android, iOS |
| [automaticallyWaitsToMinimizeStalling](#automaticallyWaitsToMinimizeStalling) | iOS |
| [backBufferDurationMs](#backBufferDurationMs) | Android |
| [bufferConfig](#bufferconfig) | Android |
@ -418,6 +419,13 @@ For this to work, the poster prop must be set.
Platforms: all
#### audioOutput
Changes the audio output.
* **speaker (default)** - plays through speaker
* **earpiece** - plays through earpiece
Platforms: Android, iOS
#### automaticallyWaitsToMinimizeStalling
A Boolean value that indicates whether the player should automatically delay playback in order to minimize stalling. For clients linked against iOS 10.0 and later
* **false** - Immediately starts playback

View File

@ -1,5 +1,7 @@
## Changelog
- Feature: playing audio over earpiece [#2887](https://github.com/react-native-video/react-native-video/issues/2887)
### Version 6.0.0-alpha.7
- All: clean JS warnings (https://github.com/react-native-video/react-native-video/pull/3183)
- Android: Add shutterView color configurtion (https://github.com/react-native-video/react-native-video/pull/3179)

View File

@ -515,6 +515,7 @@ Video.propTypes = {
disableBuffering: PropTypes.bool,
controls: PropTypes.bool,
audioOnly: PropTypes.bool,
audioOutput: PropTypes.oneOf(['earpiece', 'speaker']),
fullscreenAutorotate: PropTypes.bool,
fullscreenOrientation: PropTypes.oneOf(['all', 'landscape', 'portrait']),
progressUpdateInterval: PropTypes.number,

View File

@ -0,0 +1,31 @@
package com.brentvatne.exoplayer;
import android.annotation.SuppressLint;
import com.google.android.exoplayer2.C;
@SuppressLint("InlinedApi")
public enum AudioOutput {
SPEAKER("speaker", C.STREAM_TYPE_MUSIC),
EARPIECE("earpiece", C.STREAM_TYPE_VOICE_CALL);
private final int streamType;
private final String mName;
AudioOutput(final String name, int stream) {
mName = name;
streamType = stream;
}
public static AudioOutput get(String name) {
for (AudioOutput d : values()) {
if (d.mName.equalsIgnoreCase(name))
return d;
}
return SPEAKER;
}
@Override
public String toString() {
return getClass().getSimpleName() + "(" + this.mName + ", " + streamType + ")";
}
}

View File

@ -28,6 +28,7 @@ import androidx.activity.OnBackPressedCallback;
import com.brentvatne.common.Track;
import com.brentvatne.common.VideoTrack;
import com.brentvatne.exoplayer.AudioOutput;
import com.brentvatne.react.R;
import com.brentvatne.receiver.AudioBecomingNoisyReceiver;
import com.brentvatne.receiver.BecomingNoisyListener;
@ -48,6 +49,7 @@ import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.Tracks;
import com.google.android.exoplayer2.audio.AudioAttributes;
import com.google.android.exoplayer2.drm.DefaultDrmSessionManager;
import com.google.android.exoplayer2.drm.DefaultDrmSessionManagerProvider;
import com.google.android.exoplayer2.drm.DrmSessionEventListener;
@ -162,6 +164,7 @@ class ReactExoplayerView extends FrameLayout implements
private boolean muted = false;
private boolean hasAudioFocus = false;
private float rate = 1f;
private AudioOutput audioOutput = AudioOutput.SPEAKER;
private float audioVolume = 1f;
private int minLoadRetryCount = 3;
private int maxBitRate = 0;
@ -653,6 +656,7 @@ class ReactExoplayerView extends FrameLayout implements
PlaybackParameters params = new PlaybackParameters(rate, 1f);
player.setPlaybackParameters(params);
changeAudioOutput(this.audioOutput);
}
private DrmSessionManager initializePlayerDrm(ReactExoplayerView self) {
@ -1819,6 +1823,29 @@ class ReactExoplayerView extends FrameLayout implements
}
}
private void changeAudioOutput(AudioOutput output) {
if (player != null) {
int usage = Util.getAudioUsageForStreamType(audioOutput.streamType);
int contentType = Util.getAudioContentTypeForStreamType(audioOutput.streamType);
AudioAttributes audioAttributes = new AudioAttributes.Builder().setUsage(usage)
.setContentType(contentType)
.build();
player.setAudioAttributes(audioAttributes, false);
AudioManager audioManager = (AudioManager) themedReactContext.getSystemService(Context.AUDIO_SERVICE);
audioManager.setMode(
audioOutput == AudioOutput.SPEAKER ? AudioManager.MODE_NORMAL
: AudioManager.MODE_IN_COMMUNICATION);
audioManager.setSpeakerphoneOn(audioOutput == AudioOutput.SPEAKER);
}
}
public void setAudioOutput(AudioOutput output) {
if (audioOutput != output) {
this.audioOutput = output;
changeAudioOutput(output);
}
}
public void setVolumeModifier(float volume) {
audioVolume = volume;
if (player != null) {

View File

@ -49,6 +49,7 @@ public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerVi
private static final String PROP_TEXT_TRACKS = "textTracks";
private static final String PROP_PAUSED = "paused";
private static final String PROP_MUTED = "muted";
private static final String PROP_AUDIO_OUTPUT = "audioOutput";
private static final String PROP_VOLUME = "volume";
private static final String PROP_BACK_BUFFER_DURATION_MS = "backBufferDurationMs";
private static final String PROP_BUFFER_CONFIG = "bufferConfig";
@ -277,6 +278,11 @@ public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerVi
videoView.setMutedModifier(muted);
}
@ReactProp(name = PROP_AUDIO_OUTPUT)
public void setAudioOutput(final ReactExoplayerView videoView, final String audioOutput) {
videoView.setAudioOutput(ReactExoplayerView.AudioOutput.get(audioOutput));
}
@ReactProp(name = PROP_VOLUME, defaultFloat = 1.0f)
public void setVolume(final ReactExoplayerView videoView, final float volume) {
videoView.setVolumeModifier(volume);

View File

@ -197,7 +197,7 @@ enum RCTPlayerOperations {
var options:AVAudioSession.CategoryOptions? = nil
if (ignoreSilentSwitch == "ignore") {
category = AVAudioSession.Category.playback
category = AVAudioSession.Category.playAndRecord
} else if (ignoreSilentSwitch == "obey") {
category = AVAudioSession.Category.ambient
}

View File

@ -35,6 +35,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH
private var _controls:Bool = false
/* Keep track of any modifiers, need to be applied after each play */
private var _audioOutput: String = "speaker"
private var _volume:Float = 1.0
private var _rate:Float = 1.0
private var _maxBitRate:Float?
@ -517,6 +518,20 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH
applyModifiers()
}
@objc
func setAudioOutput(_ audioOutput:String) {
_audioOutput = audioOutput
do {
if audioOutput == "speaker" {
try AVAudioSession.sharedInstance().overrideOutputAudioPort(AVAudioSession.PortOverride.speaker)
} else if audioOutput == "earpiece" {
try AVAudioSession.sharedInstance().overrideOutputAudioPort(AVAudioSession.PortOverride.none)
}
} catch {
print("Error occurred: \(error.localizedDescription)")
}
}
@objc
func setVolume(_ volume:Float) {
_volume = volume
@ -587,6 +602,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH
setMaxBitRate(_maxBitRate)
}
setAudioOutput(_audioOutput)
setSelectedAudioTrack(_selectedAudioTrackCriteria)
setSelectedTextTrack(_selectedTextTrackCriteria)
setResizeMode(_resizeMode)

View File

@ -17,6 +17,7 @@ RCT_EXPORT_VIEW_PROPERTY(selectedAudioTrack, NSDictionary);
RCT_EXPORT_VIEW_PROPERTY(paused, BOOL);
RCT_EXPORT_VIEW_PROPERTY(muted, BOOL);
RCT_EXPORT_VIEW_PROPERTY(controls, BOOL);
RCT_EXPORT_VIEW_PROPERTY(audioOutput, NSString);
RCT_EXPORT_VIEW_PROPERTY(volume, float);
RCT_EXPORT_VIEW_PROPERTY(playInBackground, BOOL);
RCT_EXPORT_VIEW_PROPERTY(preventsDisplaySleepDuringVideoPlayback, BOOL);