Merge pull request #5485 from ByteHamster/media-player-errors
Show actual error messages instead of just the error codes
This commit is contained in:
commit
a4a9a0f4ff
|
@ -66,14 +66,6 @@ public class CancelablePSMPCallback implements PlaybackServiceMediaPlayer.PSMPCa
|
|||
return originalCallback.onMediaPlayerInfo(code, resourceId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMediaPlayerError(Object inObj, int what, int extra) {
|
||||
if (isCancelled) {
|
||||
return true;
|
||||
}
|
||||
return originalCallback.onMediaPlayerError(inObj, what, extra);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPostPlayback(@NonNull Playable media, boolean ended, boolean skipped, boolean playingNext) {
|
||||
if (isCancelled) {
|
||||
|
|
|
@ -37,11 +37,6 @@ public class DefaultPSMPCallback implements PlaybackServiceMediaPlayer.PSMPCallb
|
|||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMediaPlayerError(Object inObj, int what, int extra) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPostPlayback(@NonNull Playable media, boolean ended, boolean skipped, boolean playingNext) {
|
||||
|
||||
|
|
|
@ -514,13 +514,6 @@ public class PlaybackServiceMediaPlayerTest {
|
|||
if (assertionError == null)
|
||||
assertionError = new AssertionFailedError("Unexpected call to shouldStop");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMediaPlayerError(Object inObj, int what, int extra) {
|
||||
if (assertionError == null)
|
||||
assertionError = new AssertionFailedError("Unexpected call to onMediaPlayerError");
|
||||
return false;
|
||||
}
|
||||
});
|
||||
PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback);
|
||||
Playable p = writeTestPlayable(playableFileUrl, PLAYABLE_LOCAL_URL);
|
||||
|
@ -604,14 +597,6 @@ public class PlaybackServiceMediaPlayerTest {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMediaPlayerError(Object inObj, int what, int extra) {
|
||||
if (assertionError == null) {
|
||||
assertionError = new AssertionFailedError("Unexpected call of onMediaPlayerError");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback);
|
||||
if (initialState == PlayerStatus.PREPARED || initialState == PlayerStatus.PLAYING || initialState == PlayerStatus.PAUSED) {
|
||||
|
@ -664,13 +649,6 @@ public class PlaybackServiceMediaPlayerTest {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMediaPlayerError(Object inObj, int what, int extra) {
|
||||
if (assertionError == null)
|
||||
assertionError = new AssertionFailedError("Unexpected call to onMediaPlayerError");
|
||||
return false;
|
||||
}
|
||||
});
|
||||
PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback);
|
||||
Playable p = writeTestPlayable(playableFileUrl, PLAYABLE_LOCAL_URL);
|
||||
|
@ -738,13 +716,6 @@ public class PlaybackServiceMediaPlayerTest {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMediaPlayerError(Object inObj, int what, int extra) {
|
||||
if (assertionError == null)
|
||||
assertionError = new AssertionFailedError("Unexpected call to onMediaPlayerError");
|
||||
return false;
|
||||
}
|
||||
});
|
||||
PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback);
|
||||
Playable p = writeTestPlayable(playableFileUrl, PLAYABLE_LOCAL_URL);
|
||||
|
|
|
@ -37,6 +37,7 @@ import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
|
|||
import com.bumptech.glide.Glide;
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.core.event.PlaybackPositionEvent;
|
||||
import de.danoeh.antennapod.core.event.PlayerErrorEvent;
|
||||
import de.danoeh.antennapod.core.event.ServiceEvent;
|
||||
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
||||
import de.danoeh.antennapod.core.service.playback.PlaybackService;
|
||||
|
@ -50,7 +51,6 @@ import de.danoeh.antennapod.core.util.ShareUtils;
|
|||
import de.danoeh.antennapod.core.util.StorageUtils;
|
||||
import de.danoeh.antennapod.core.util.TimeSpeedConverter;
|
||||
import de.danoeh.antennapod.core.util.gui.PictureInPictureUtil;
|
||||
import de.danoeh.antennapod.core.util.playback.MediaPlayerError;
|
||||
import de.danoeh.antennapod.core.util.playback.PlaybackController;
|
||||
import de.danoeh.antennapod.databinding.VideoplayerActivityBinding;
|
||||
import de.danoeh.antennapod.dialog.PlaybackControlsDialog;
|
||||
|
@ -207,15 +207,6 @@ public class VideoplayerActivity extends CastEnabledActivity implements SeekBar.
|
|||
viewBinding.sbPosition.setSecondaryProgress((int) (progress * viewBinding.sbPosition.getMax()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleError(int code) {
|
||||
final AlertDialog.Builder errorDialog = new AlertDialog.Builder(VideoplayerActivity.this);
|
||||
errorDialog.setTitle(R.string.error_label);
|
||||
errorDialog.setMessage(MediaPlayerError.getErrorString(VideoplayerActivity.this, code));
|
||||
errorDialog.setNeutralButton(android.R.string.ok, (dialog, which) -> finish());
|
||||
errorDialog.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReloadNotification(int code) {
|
||||
VideoplayerActivity.this.onReloadNotification(code);
|
||||
|
@ -550,6 +541,15 @@ public class VideoplayerActivity extends CastEnabledActivity implements SeekBar.
|
|||
}
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
public void onMediaPlayerError(PlayerErrorEvent event) {
|
||||
final AlertDialog.Builder errorDialog = new AlertDialog.Builder(VideoplayerActivity.this);
|
||||
errorDialog.setTitle(R.string.error_label);
|
||||
errorDialog.setMessage(event.getMessage());
|
||||
errorDialog.setNeutralButton(android.R.string.ok, (dialog, which) -> finish());
|
||||
errorDialog.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
super.onCreateOptionsMenu(menu);
|
||||
|
|
|
@ -25,6 +25,7 @@ import androidx.viewpager2.widget.ViewPager2;
|
|||
import com.google.android.material.bottomsheet.BottomSheetBehavior;
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
|
||||
import de.danoeh.antennapod.core.event.PlayerErrorEvent;
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.greenrobot.eventbus.Subscribe;
|
||||
import org.greenrobot.eventbus.ThreadMode;
|
||||
|
@ -50,7 +51,6 @@ import de.danoeh.antennapod.core.util.ChapterUtils;
|
|||
import de.danoeh.antennapod.core.util.Converter;
|
||||
import de.danoeh.antennapod.core.util.IntentUtils;
|
||||
import de.danoeh.antennapod.core.util.TimeSpeedConverter;
|
||||
import de.danoeh.antennapod.core.util.playback.MediaPlayerError;
|
||||
import de.danoeh.antennapod.model.playback.Playable;
|
||||
import de.danoeh.antennapod.core.util.playback.PlaybackController;
|
||||
import de.danoeh.antennapod.dialog.PlaybackControlsDialog;
|
||||
|
@ -300,23 +300,6 @@ public class AudioPlayerFragment extends Fragment implements
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleError(int code) {
|
||||
final AlertDialog.Builder errorDialog = new AlertDialog.Builder(getContext());
|
||||
errorDialog.setTitle(R.string.error_label);
|
||||
errorDialog.setMessage(MediaPlayerError.getErrorString(getContext(), code));
|
||||
errorDialog.setPositiveButton(android.R.string.ok, (dialog, which) ->
|
||||
((MainActivity) getActivity()).getBottomSheet().setState(BottomSheetBehavior.STATE_COLLAPSED));
|
||||
if (!UserPreferences.useExoplayer()) {
|
||||
errorDialog.setNeutralButton(R.string.media_player_switch_to_exoplayer, (dialog, which) -> {
|
||||
UserPreferences.enableExoplayer();
|
||||
((MainActivity) getActivity()).showSnackbarAbovePlayer(
|
||||
R.string.media_player_switched_to_exoplayer, Snackbar.LENGTH_LONG);
|
||||
});
|
||||
}
|
||||
errorDialog.create().show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSleepTimerUpdate() {
|
||||
AudioPlayerFragment.this.loadMediaInfo(false);
|
||||
|
@ -419,6 +402,23 @@ public class AudioPlayerFragment extends Fragment implements
|
|||
AudioPlayerFragment.this.loadMediaInfo(false);
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
public void mediaPlayerError(PlayerErrorEvent event) {
|
||||
final AlertDialog.Builder errorDialog = new AlertDialog.Builder(getContext());
|
||||
errorDialog.setTitle(R.string.error_label);
|
||||
errorDialog.setMessage(event.getMessage());
|
||||
errorDialog.setPositiveButton(android.R.string.ok, (dialog, which) ->
|
||||
((MainActivity) getActivity()).getBottomSheet().setState(BottomSheetBehavior.STATE_COLLAPSED));
|
||||
if (!UserPreferences.useExoplayer()) {
|
||||
errorDialog.setNeutralButton(R.string.media_player_switch_to_exoplayer, (dialog, which) -> {
|
||||
UserPreferences.enableExoplayer();
|
||||
((MainActivity) getActivity()).showSnackbarAbovePlayer(
|
||||
R.string.media_player_switched_to_exoplayer, Snackbar.LENGTH_LONG);
|
||||
});
|
||||
}
|
||||
errorDialog.create().show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
|
||||
if (controller == null || txtvLength == null) {
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
package de.danoeh.antennapod.core.event;
|
||||
|
||||
public class PlayerErrorEvent {
|
||||
private final String message;
|
||||
|
||||
public PlayerErrorEvent(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
}
|
|
@ -5,6 +5,8 @@ import android.net.Uri;
|
|||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.SurfaceHolder;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.util.Consumer;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.DefaultLoadControl;
|
||||
import com.google.android.exoplayer2.DefaultRenderersFactory;
|
||||
|
@ -54,7 +56,7 @@ public class ExoPlayerWrapper implements IPlayer {
|
|||
private MediaSource mediaSource;
|
||||
private MediaPlayer.OnSeekCompleteListener audioSeekCompleteListener;
|
||||
private MediaPlayer.OnCompletionListener audioCompletionListener;
|
||||
private MediaPlayer.OnErrorListener audioErrorListener;
|
||||
private Consumer<String> audioErrorListener;
|
||||
private MediaPlayer.OnBufferingUpdateListener bufferingUpdateListener;
|
||||
private PlaybackParameters playbackParameters;
|
||||
private MediaPlayer.OnInfoListener infoListener;
|
||||
|
@ -98,9 +100,11 @@ public class ExoPlayerWrapper implements IPlayer {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onPlayerError(ExoPlaybackException error) {
|
||||
public void onPlayerError(@NonNull ExoPlaybackException error) {
|
||||
if (audioErrorListener != null) {
|
||||
audioErrorListener.onError(null, error.type + ERROR_CODE_OFFSET, 0);
|
||||
Throwable cause = error.getCause();
|
||||
audioErrorListener.accept(cause != null
|
||||
? cause.getLocalizedMessage() : error.getLocalizedMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -323,7 +327,7 @@ public class ExoPlayerWrapper implements IPlayer {
|
|||
this.audioSeekCompleteListener = audioSeekCompleteListener;
|
||||
}
|
||||
|
||||
void setOnErrorListener(MediaPlayer.OnErrorListener audioErrorListener) {
|
||||
void setOnErrorListener(Consumer<String> audioErrorListener) {
|
||||
this.audioErrorListener = audioErrorListener;
|
||||
}
|
||||
|
||||
|
|
|
@ -14,7 +14,9 @@ import android.view.SurfaceHolder;
|
|||
import androidx.media.AudioAttributesCompat;
|
||||
import androidx.media.AudioFocusRequestCompat;
|
||||
import androidx.media.AudioManagerCompat;
|
||||
import de.danoeh.antennapod.core.event.PlayerErrorEvent;
|
||||
import de.danoeh.antennapod.core.storage.DBReader;
|
||||
import de.danoeh.antennapod.core.util.playback.MediaPlayerError;
|
||||
import org.antennapod.audio.MediaPlayer;
|
||||
|
||||
import java.io.File;
|
||||
|
@ -41,6 +43,7 @@ import de.danoeh.antennapod.core.util.playback.IPlayer;
|
|||
import de.danoeh.antennapod.model.playback.Playable;
|
||||
import de.danoeh.antennapod.core.util.playback.PlaybackServiceStarter;
|
||||
import de.danoeh.antennapod.core.util.playback.VideoPlayer;
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
|
||||
/**
|
||||
* Manages the MediaPlayer object of the PlaybackService.
|
||||
|
@ -294,6 +297,7 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
|
|||
} catch (IOException | IllegalStateException e) {
|
||||
e.printStackTrace();
|
||||
setPlayerStatus(PlayerStatus.ERROR, null);
|
||||
EventBus.getDefault().postSticky(new PlayerErrorEvent(e.getLocalizedMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -402,6 +406,7 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
|
|||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
setPlayerStatus(PlayerStatus.ERROR, null);
|
||||
EventBus.getDefault().postSticky(new PlayerErrorEvent(e.getLocalizedMessage()));
|
||||
}
|
||||
}
|
||||
playerLock.unlock();
|
||||
|
@ -734,7 +739,7 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
|
|||
ap.setOnErrorListener((mediaPlayer, i, i1) -> true);
|
||||
} else if (mediaPlayer instanceof ExoPlayerWrapper) {
|
||||
ExoPlayerWrapper ap = (ExoPlayerWrapper) mediaPlayer;
|
||||
ap.setOnErrorListener((mediaPlayer, i, i1) -> true);
|
||||
ap.setOnErrorListener(message -> { });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1033,7 +1038,7 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
|
|||
ap.setOnCompletionListener(audioCompletionListener);
|
||||
ap.setOnSeekCompleteListener(audioSeekCompleteListener);
|
||||
ap.setOnBufferingUpdateListener(audioBufferingUpdateListener);
|
||||
ap.setOnErrorListener(audioErrorListener);
|
||||
ap.setOnErrorListener(message -> EventBus.getDefault().postSticky(new PlayerErrorEvent(message)));
|
||||
ap.setOnInfoListener(audioInfoListener);
|
||||
} else {
|
||||
Log.w(TAG, "Unknown media player: " + mp);
|
||||
|
@ -1084,7 +1089,8 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
|
|||
private final android.media.MediaPlayer.OnErrorListener videoErrorListener = this::genericOnError;
|
||||
|
||||
private boolean genericOnError(Object inObj, int what, int extra) {
|
||||
return callback.onMediaPlayerError(inObj, what, extra);
|
||||
EventBus.getDefault().postSticky(new PlayerErrorEvent(MediaPlayerError.getErrorString(context, what)));
|
||||
return true;
|
||||
}
|
||||
|
||||
private final MediaPlayer.OnSeekCompleteListener audioSeekCompleteListener =
|
||||
|
|
|
@ -43,6 +43,7 @@ import androidx.core.app.NotificationManagerCompat;
|
|||
import androidx.media.MediaBrowserServiceCompat;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import de.danoeh.antennapod.core.event.PlayerErrorEvent;
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.greenrobot.eventbus.Subscribe;
|
||||
import org.greenrobot.eventbus.ThreadMode;
|
||||
|
@ -161,7 +162,6 @@ public class PlaybackService extends MediaBrowserServiceCompat {
|
|||
public static final int EXTRA_CODE_VIDEO = 2;
|
||||
public static final int EXTRA_CODE_CAST = 3;
|
||||
|
||||
public static final int NOTIFICATION_TYPE_ERROR = 0;
|
||||
public static final int NOTIFICATION_TYPE_BUFFER_UPDATE = 2;
|
||||
|
||||
/**
|
||||
|
@ -938,19 +938,6 @@ public class PlaybackService extends MediaBrowserServiceCompat {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMediaPlayerError(Object inObj, int what, int extra) {
|
||||
final String TAG = "PlaybackSvc.onErrorLtsn";
|
||||
Log.w(TAG, "An error has occured: " + what + " " + extra);
|
||||
if (mediaPlayer.getPlayerStatus() == PlayerStatus.PLAYING) {
|
||||
mediaPlayer.pause(true, false);
|
||||
}
|
||||
sendNotificationBroadcast(NOTIFICATION_TYPE_ERROR, what);
|
||||
PlaybackPreferences.writeNoMediaPlaying();
|
||||
stateManager.stopService();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPostPlayback(@NonNull Playable media, boolean ended, boolean skipped,
|
||||
boolean playingNext) {
|
||||
|
@ -996,6 +983,16 @@ public class PlaybackService extends MediaBrowserServiceCompat {
|
|||
}
|
||||
};
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
@SuppressWarnings("unused")
|
||||
public void playerError(PlayerErrorEvent event) {
|
||||
if (mediaPlayer.getPlayerStatus() == PlayerStatus.PLAYING) {
|
||||
mediaPlayer.pause(true, false);
|
||||
}
|
||||
PlaybackPreferences.writeNoMediaPlaying();
|
||||
stateManager.stopService();
|
||||
}
|
||||
|
||||
private Playable getNextInQueue(final Playable currentMedia) {
|
||||
if (!(currentMedia instanceof FeedMedia)) {
|
||||
Log.d(TAG, "getNextInQueue(), but playable not an instance of FeedMedia, so not proceeding");
|
||||
|
|
|
@ -356,8 +356,6 @@ public abstract class PlaybackServiceMediaPlayer {
|
|||
|
||||
boolean onMediaPlayerInfo(int code, @StringRes int resourceId);
|
||||
|
||||
boolean onMediaPlayerError(Object inObj, int what, int extra);
|
||||
|
||||
void onPostPlayback(@NonNull Playable media, boolean ended, boolean skipped, boolean playingNext);
|
||||
|
||||
void onPlaybackStart(@NonNull Playable playable, int position);
|
||||
|
|
|
@ -7,14 +7,11 @@ import android.content.Context;
|
|||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.ServiceConnection;
|
||||
import android.media.MediaPlayer;
|
||||
import android.os.IBinder;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import android.view.SurfaceHolder;
|
||||
import androidx.annotation.NonNull;
|
||||
import de.danoeh.antennapod.core.R;
|
||||
import de.danoeh.antennapod.core.event.MessageEvent;
|
||||
import de.danoeh.antennapod.core.event.ServiceEvent;
|
||||
import de.danoeh.antennapod.model.playback.MediaType;
|
||||
import de.danoeh.antennapod.core.feed.util.PlaybackSpeedUtils;
|
||||
|
@ -209,9 +206,6 @@ public abstract class PlaybackController {
|
|||
return;
|
||||
}
|
||||
switch (type) {
|
||||
case PlaybackService.NOTIFICATION_TYPE_ERROR:
|
||||
handleError(code);
|
||||
break;
|
||||
case PlaybackService.NOTIFICATION_TYPE_BUFFER_UPDATE:
|
||||
float progress = ((float) code) / 100;
|
||||
onBufferUpdate(progress);
|
||||
|
@ -264,8 +258,6 @@ public abstract class PlaybackController {
|
|||
|
||||
public void onSleepTimerUpdate() {}
|
||||
|
||||
public void handleError(int code) {}
|
||||
|
||||
public void onPlaybackEnd() {}
|
||||
|
||||
/**
|
||||
|
@ -276,10 +268,6 @@ public abstract class PlaybackController {
|
|||
Log.d(TAG, "status: " + status.toString());
|
||||
checkMediaInfoLoaded();
|
||||
switch (status) {
|
||||
case ERROR:
|
||||
EventBus.getDefault().post(new MessageEvent(activity.getString(R.string.player_error_msg)));
|
||||
handleError(MediaPlayer.MEDIA_ERROR_UNKNOWN);
|
||||
break;
|
||||
case PAUSED:
|
||||
onPositionObserverUpdate();
|
||||
updatePlayButtonShowsPlay(true);
|
||||
|
@ -555,20 +543,6 @@ public abstract class PlaybackController {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Move service into INITIALIZED state if it's paused to save bandwidth
|
||||
*/
|
||||
public void reinitServiceIfPaused() {
|
||||
if (playbackService != null
|
||||
&& playbackService.isStreaming()
|
||||
&& !PlaybackService.isCasting()
|
||||
&& (playbackService.getStatus() == PlayerStatus.PAUSED ||
|
||||
(playbackService.getStatus() == PlayerStatus.PREPARING &&
|
||||
!playbackService.isStartWhenPrepared()))) {
|
||||
playbackService.reinit();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isStreaming() {
|
||||
return playbackService != null && playbackService.isStreaming();
|
||||
}
|
||||
|
|
|
@ -295,7 +295,6 @@
|
|||
<string name="confirm_mobile_download_dialog_enable_temporarily">Allow temporarily</string>
|
||||
|
||||
<!-- Mediaplayer messages -->
|
||||
<string name="player_error_msg">Error!</string>
|
||||
<string name="playback_error_server_died">Server died</string>
|
||||
<string name="playback_error_unsupported">Unsupported media type</string>
|
||||
<string name="playback_error_timeout">Operation timed out</string>
|
||||
|
|
Loading…
Reference in New Issue