I'm tying to play videos from a Firebase Listadapter. When the user clicks on the list they a video plays, and upon finishing the activity finishes, and they return to the original list.
When the click on the second video it starts the ExoPlayer activity, but won't play the video. If they press back and select the original video it will play fine.
public String mTastingWineID;
public String tastingWineId;
public String mWineVideoID;
public String tastingWineVideoId;
private DataSource.Factory mediaDataSourceFactory;
private boolean isPlaying = false;
public int mVideoResource;
public Uri videoUri;
private static final String TAG = "WineMediaActivity";
private static final DefaultBandwidthMeter BANDWIDTH_METER = new DefaultBandwidthMeter();
private SimpleExoPlayer player;
private SimpleExoPlayerView mVideoView;
private ComponentListener componentListener;
private long playbackPostiion;
private int currentWindow;
private boolean playWhenReady = true;
private BandwidthMeter bandwidthMeter;
public Firebase wineMediaUrl;
public ValueEventListener wineMediaUrlListener;
private MediaControllerCompat mediaControllerCompat;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.analytics_media_wine_information);
Bundle bundle = getIntent().getExtras();
mTastingWineID = bundle.getString(Constants.WINE_ID);
tastingWineId = mTastingWineID.toString();
mWineVideoID = bundle.getString(Constants.WINE_VIDEO_ID);
tastingWineVideoId = mWineVideoID.toString();
if (player != null){
player.release();
}
getCurrentVideo();
}
public void getCurrentVideo(){
wineMediaUrl = new Firebase(Constants.FIREBASE_URL).child(FIREBASE_LOCATION_WINE_DETAILS).child(tastingWineId).child(WINE_MEDIA).child(tastingWineVideoId).child("wineVideoUrl");
wineMediaUrlListener = wineMediaUrl.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
componentListener = new ComponentListener();
mVideoView = findViewById(R.id.videoView1);
mVideoView.requestFocus();
Object tempVideoFiles = dataSnapshot.getValue();
String str = tempVideoFiles.toString();
videoUri = Uri.parse(str);
if (videoUri != null) {
initializePlayer();
} else {
Toast.makeText(WineMediaActivity.this, "No Video Found", Toast.LENGTH_SHORT).show();
}
}
#Override
public void onCancelled(FirebaseError firebaseError) {
}
});
mediaDataSourceFactory = new DefaultDataSourceFactory(this, Util.getUserAgent(this, "mediaPlayerSample"), (TransferListener<? super DataSource>) bandwidthMeter);
}
private class ComponentListener implements ExoPlayer.EventListener, VideoRendererEventListener, AudioRendererEventListener {
#Override
public void onTimelineChanged(Timeline timeline, Object manifest) {
}
#Override
public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {
}
#Override
public void onLoadingChanged(boolean isLoading) {
}
#Override
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
String stateString;
switch (playbackState) {
case ExoPlayer.STATE_IDLE:
stateString = "ExoPlayer.STATE_IDLE";
break;
case ExoPlayer.STATE_BUFFERING:
stateString = "ExoPlayer.STATE_IDLE";
break;
case ExoPlayer.STATE_READY:
stateString = "ExoPlayer.STATE_IDLE";
break;
case ExoPlayer.STATE_ENDED:
stateString = "ExoPlayer.STATE_IDLE";
releasePlayer();
finish();
break;
default:
stateString = "UNKNOWN_STATE";
break;
}
Log.d(TAG, "changed state to: " + stateString + " play when ready: " + playWhenReady);
}
#Override
public void onRepeatModeChanged(int repeatMode) {
}
#Override
public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) {
}
#Override
public void onPlayerError(ExoPlaybackException error) {
}
#Override
public void onPositionDiscontinuity(int reason) {
}
#Override
public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {
}
#Override
public void onSeekProcessed() {
}
#Override
public void onAudioEnabled(DecoderCounters counters) {
}
#Override
public void onAudioSessionId(int audioSessionId) {
}
#Override
public void onAudioDecoderInitialized(String decoderName, long initializedTimestampMs, long initializationDurationMs) {
}
#Override
public void onAudioInputFormatChanged(Format format) {
}
#Override
public void onAudioSinkUnderrun(int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) {
}
#Override
public void onAudioDisabled(DecoderCounters counters) {
}
#Override
public void onVideoEnabled(DecoderCounters counters) {
}
#Override
public void onVideoDecoderInitialized(String decoderName, long initializedTimestampMs, long initializationDurationMs) {
}
#Override
public void onVideoInputFormatChanged(Format format) {
}
#Override
public void onDroppedFrames(int count, long elapsedMs) {
}
#Override
public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) {
}
#Override
public void onRenderedFirstFrame(Surface surface) {
}
#Override
public void onVideoDisabled(DecoderCounters counters) {
}
}
private void initializePlayer() {
if (player == null) {
TrackSelection.Factory adaptiveTrackSelectionFactory = new AdaptiveTrackSelection.Factory(bandwidthMeter);
player = ExoPlayerFactory.newSimpleInstance(new DefaultRenderersFactory(this), new DefaultTrackSelector(adaptiveTrackSelectionFactory), new DefaultLoadControl());
player.addListener(componentListener);
player.setVideoDebugListener(componentListener);
player.setAudioDebugListener(componentListener);
mVideoView.setPlayer(player);
player.setPlayWhenReady(playWhenReady);
player.seekTo(currentWindow, playbackPostiion);
}
MediaSource mediaSource = new ExtractorMediaSource(videoUri, mediaDataSourceFactory, extractorsFactory, null, null);
player.prepare(mediaSource, true, false);
}
#SuppressLint("InlinedApi")
private void hideSystemUi() {
mVideoView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE | View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
}
private void releasePlayer() {
if (player != null) {
playbackPostiion = player.getCurrentPosition();
currentWindow = player.getCurrentWindowIndex();
playWhenReady = player.getPlayWhenReady();
player.removeListener(componentListener);
mVideoView.setPlayer(null);
player.setVideoListener(null);
player.setVideoDebugListener(null);
player.setAudioDebugListener(null);
player.release();
player = null;
if (wineMediaUrlListener != null) {
wineMediaUrl.removeEventListener(wineMediaUrlListener);
wineMediaUrlListener = null;
}
}
}
#Override
protected void onPause() {
super.onPause();
releasePlayer();
}
#Override
public void onStop() {
super.onStop();
releasePlayer();
}
#Override
public void onDestroy() {
super.onDestroy();
releasePlayer();
}
Apologies for the code, I'm newish to coding and hoping someone can help me.
Turns out the code is fine - the error was in the upload of videos, they were overwriting the previous video meaning the previous one wouldn't play.
Related
Cannot get sinch call when app is clear from recent in android
public class SinchService extends Service {
private static final String APP_KEY = "";
private static final String APP_SECRET = "";
private static final String ENVIRONMENT = "clientapi.sinch.com";
public static final String CALL_ID = "CALL_ID";
static final String TAG = SinchService.class.getSimpleName();
private SinchServiceInterface mSinchServiceInterface = new SinchServiceInterface();
private SinchClient mSinchClient;
private String mUserId;
private StartFailedListener mListener;
#Override
public void onCreate() {
super.onCreate();
}
private void start(String userName) {
Log.d("start123", "start");
if (mSinchClient == null) {
mUserId = userName;
mSinchClient = Sinch.getSinchClientBuilder().context(getApplicationContext()).userId(userName)
.applicationKey(APP_KEY)
.applicationSecret(APP_SECRET)
.environmentHost(ENVIRONMENT).build();
mSinchClient.setSupportManagedPush(true);
mSinchClient.setSupportCalling(true);
mSinchClient.startListeningOnActiveConnection();
mSinchClient.setSupportActiveConnectionInBackground(true);
mSinchClient.addSinchClientListener(new MySinchClientListener());
mSinchClient.getCallClient().addCallClientListener(new SinchCallClientListener());
mSinchClient.start();
}
}
private void stop() {
if (mSinchClient != null) {
mSinchClient.terminate();
mSinchClient = null;
}
}
private boolean isStarted() {
return (mSinchClient != null && mSinchClient.isStarted());
}
#Override
public IBinder onBind(Intent intent) {
return mSinchServiceInterface;
}
public class SinchServiceInterface extends Binder {
public Call callUserVideo(String userId) {
return mSinchClient.getCallClient().callUser(userId);
}
public Call callUser(String userId) {
return mSinchClient.getCallClient().callUserVideo(userId);
}
public String getUserName() {
return mUserId;
}
public boolean isStarted() {
return SinchService.this.isStarted();
}
public void startClient(String userName) {
start(userName);
}
public void stopClient() {
stop();
}
public void setStartListener(StartFailedListener listener) {
mListener = listener;
}
public Call getCall(String callId) {
return mSinchClient.getCallClient().getCall(callId);
}
public VideoController getVideoController() {
if (!isStarted()) {
return null;
}
return mSinchClient.getVideoController();
}
public AudioController getAudioController() {
if (!isStarted()) {
return null;
}
return mSinchClient.getAudioController();
}
}
public interface StartFailedListener {
void onStartFailed(SinchError error);
void onStarted();
}
private class MySinchClientListener implements SinchClientListener {
#Override
public void onClientFailed(SinchClient client, SinchError error) {
if (mListener != null) {
mListener.onStartFailed(error);
}
mSinchClient.terminate();
mSinchClient = null;
}
#Override
public void onClientStarted(SinchClient client) {
Log.d(TAG, "SinchClient started");
if (mListener != null) {
mListener.onStarted();
}
}
#Override
public void onClientStopped(SinchClient client) {
Log.d(TAG, "SinchClient stopped");
}
#Override
public void onLogMessage(int level, String area, String message) {
switch (level) {
case Log.DEBUG:
Log.d(area, message);
break;
case Log.ERROR:
Log.e(area, message);
break;
case Log.INFO:
Log.i(area, message);
break;
case Log.VERBOSE:
Log.v(area, message);
break;
case Log.WARN:
Log.w(area, message);
break;
}
}
#Override
public void onRegistrationCredentialsRequired(SinchClient client,
ClientRegistration clientRegistration) {
}
}
private class SinchCallClientListener implements CallClientListener {
#Override
public void onIncomingCall(CallClient callClient, Call call) {
FirebaseAuth auth = FirebaseAuth.getInstance();
DatabaseReference cal = FirebaseDatabase.getInstance().getReference().child("call_detail").child(auth.getCurrentUser().getUid()).child(call.getCallId());
cal.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
if (dataSnapshot.exists()) {
String cl = dataSnapshot.child("callType").getValue(String.class);
if( cl.equals("audio") ){
Intent intent = new Intent(SinchService.this, IncomingVoiceCallActivity.class);
intent.putExtra(CALL_ID, call.getCallId());
intent.putExtra("callerUserId", call.getRemoteUserId());
Log.d("ABCD", call.getRemoteUserId());
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
SinchService.this.startActivity(intent);
}if (cl.equals("video")){
Intent intent = new Intent(SinchService.this, IncomingVideoCallActivity.class);
intent.putExtra(CALL_ID, call.getCallId());
intent.putExtra("callerUserId", call.getRemoteUserId());
Log.d("ABCD", call.getRemoteUserId());
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
SinchService.this.startActivity(intent);
}
}
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});
}
}
}
I received call when app is in recent tray. but when i clear app from recent i cannot get call. this is my SinchService class. i also used forground service, with foreground service i get call on some devices on app close but not get call on some oppo and samsung mobiles.
FCM with agora Implementation
I have down agora part but have to implement firebase console.
I have configured cm. Previously I was using sinch to have called between two apps but now want to change to calling with agoro but with the same concept. In that, we were sending a token to the server and according to that sinch use to handle the call.
public class MainActivity extends AppCompatActivity {
private static final int PERMISSION_REQ_ID = 22;
private static final String[] REQUESTED_PERMISSIONS = {
Manifest.permission.RECORD_AUDIO,
Manifest.permission.CAMERA,
Manifest.permission.WRITE_EXTERNAL_STORAGE
};
private static final String TAG="Agora ";
private RtcEngine mRtcEmgine;
private FrameLayout mLocalContainer;
private RelativeLayout mRemoteContainer;
private SurfaceView mLocalView;
private SurfaceView mremoteView;
private ImageView mCallBtn;
private ImageView mMuteBtn;
private ImageView mSwitchCameraBtn;
private boolean mCallEnd;
private boolean mMuted;
private final IRtcEngineEventHandler mRtcHandler= new IRtcEngineEventHandler() {
#Override
public void onJoinChannelSuccess(String channel,final int uid, int elapsed) {
super.onJoinChannelSuccess(channel, uid, elapsed);
runOnUiThread(new Runnable() {
#Override
public void run() {
Log.e("agora","Join channel success, uid "+(uid &0xFFFFFFFFL));
}
});
}
#Override
public void onUserOffline(final int uid, int reason) {
super.onUserOffline(uid, reason);
runOnUiThread(new Runnable() {
#Override
public void run() {
Log.e("agora","User Offline, uid "+(uid &0xFFFFFFFFL));
removeRemoteVideo();
}
});
}
#Override
public void onRemoteVideoStateChanged(final int uid, int state, int reason, int elapsed) {
super.onRemoteVideoStateChanged(uid, state, reason, elapsed);
runOnUiThread(new Runnable() {
#Override
public void run() {
Log.e("agora","First reomte video decoded, uid "+(uid &0xFFFFFFFFL));
setupRemoteVideo(uid);
}
});
}
};
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//initUI();
mLocalContainer=findViewById(R.id.local_video_view_container);
mRemoteContainer=findViewById(R.id.remote_video_view_container);
mCallBtn=findViewById(R.id.btn_call);
mCallBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (mCallEnd){
startCall();
mCallEnd=false;
mCallBtn.setImageResource(R.drawable.btn_endcall);
}
else {
endCall();
mCallEnd=true;
mCallBtn.setImageResource(R.drawable.btn_startcall);
}
showButton(!mCallEnd);
}
});
mMuteBtn=findViewById(R.id.btn_mute);
mMuteBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
mMuted = !mMuted;
mRtcEmgine.muteLocalAudioStream(mMuted);
int res = mMuted ? R.drawable.btn_mute : R.drawable.btn_unmute;
mMuteBtn.setImageResource(res);
}
});
mSwitchCameraBtn=findViewById(R.id.btn_switch_camera);
mSwitchCameraBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
mRtcEmgine.switchCamera();
}
});
if (checkSelfpermission(REQUESTED_PERMISSIONS[0]) &&
checkSelfpermission(REQUESTED_PERMISSIONS[1]) &&
checkSelfpermission(REQUESTED_PERMISSIONS[2]))
{
Log.e(TAG," true ");
initEngineAndJoinChannel();
}
else {
Log.e(TAG," false ");
}
}
#Override
public void onRequestPermissionsResult(int requestCode, #NonNull String[] permissions, #NonNull int[] grantResults) {
if (requestCode == PERMISSION_REQ_ID) {
if (grantResults[0] != PackageManager.PERMISSION_GRANTED ||
grantResults[1] != PackageManager.PERMISSION_GRANTED ||
grantResults[2] != PackageManager.PERMISSION_GRANTED) {
Log.e(TAG,"Need permissions " + Manifest.permission.RECORD_AUDIO +
"/" + Manifest.permission.CAMERA + "/" + Manifest.permission.WRITE_EXTERNAL_STORAGE);
finish();
return;
}
initEngineAndJoinChannel();
}
}
#Override
protected void onDestroy(){
super.onDestroy();
if (!mCallEnd){
leaveChannel();
}
RtcEngine.destroy();
}
private void initUI() {
Log.e(TAG," UI ");
mLocalContainer=findViewById(R.id.local_video_view_container);
mRemoteContainer=findViewById(R.id.remote_video_view_container);
mCallBtn=findViewById(R.id.btn_call);
mMuteBtn=findViewById(R.id.btn_mute);
mSwitchCameraBtn=findViewById(R.id.btn_switch_camera);
}
private void initEngineAndJoinChannel() {
initializeEngine();
setupVideoConfig();
setupLocalVideo();
joinChannel();
}
private void setupVideoConfig() {
Log.e(TAG,"running 2");
mRtcEmgine.enableVideo();
mRtcEmgine.setVideoEncoderConfiguration(new VideoEncoderConfiguration(
VideoEncoderConfiguration.VD_640x360,
VideoEncoderConfiguration.FRAME_RATE.FRAME_RATE_FPS_15,
VideoEncoderConfiguration.STANDARD_BITRATE,
VideoEncoderConfiguration.ORIENTATION_MODE.ORIENTATION_MODE_FIXED_PORTRAIT
));
}
private void setupLocalVideo() {
Log.e(TAG,"running 3");
mRtcEmgine.enableVideo();
mLocalView=RtcEngine.CreateRendererView(getBaseContext());
mLocalView.setZOrderMediaOverlay(true);
mLocalContainer.addView(mLocalView);
VideoCanvas localVideoCanvas = new VideoCanvas(mLocalView, VideoCanvas.RENDER_MODE_HIDDEN,0);
mRtcEmgine.setupLocalVideo(localVideoCanvas);
}
private void setupRemoteVideo(int uid) {
int count = mRemoteContainer.getChildCount();
View view = null;
for(int i = 0; i < count; i++) {
View v= mRemoteContainer.getChildAt(i);
if (v.getTag() instanceof Integer && ((int)v.getTag()) == uid) {
view = v;
}
}
if (view != null) {
return;
}
mremoteView = RtcEngine.CreateRendererView(getBaseContext());
mRemoteContainer.addView(mremoteView);
mRtcEmgine.setupRemoteVideo(new VideoCanvas(mremoteView, VideoCanvas.RENDER_MODE_HIDDEN, uid));
mremoteView.setTag(uid);
}
private void initializeEngine() {
Log.e(TAG,"running 1");
try {
mRtcEmgine=RtcEngine.create(getBaseContext(),getString(R.string.agora_app_id),mRtcHandler);
}
catch (Exception e) {
Log.e(TAG,Log.getStackTraceString(e));
throw new RuntimeException("NEED TO check rtc sdk fatal error \n"+Log.getStackTraceString(e));
}
}
private void removeRemoteVideo() {
if (mremoteView != null) {
mRemoteContainer.removeView(mremoteView);
}
mremoteView = null;
}
private void joinChannel() {
Log.e(TAG,"running 4");
String token = getString(R.string.agora_access_token);
if (TextUtils.isEmpty(token)) {
token = null;
}
mRtcEmgine.joinChannel(token,"demochannel", "", 0);
}
private void leaveChannel() {
mRtcEmgine.leaveChannel();
}
/* public void onLocalAudioMuteClicked(View view) {
mMuted = !mMuted;
mRtcEmgine.muteLocalAudioStream(mMuted);
int res = mMuted ? R.drawable.btn_mute : R.drawable.btn_unmute;
mMuteBtn.setImageResource(res);
}*/
/* public void onSwitchClicked(View view) {
mRtcEmgine.switchCamera();
}*/
/* public void onCallClicked(View view) {
if (mCallEnd){
startCall();
mCallEnd = false;
mCallBtn.setImageResource(R.drawable.btn_endcall);
}
else {
endCall();
mCallEnd = true;
mCallBtn.setImageResource(R.drawable.btn_startcall);
}
showButton(!mCallEnd);
}*/
private void startCall() {
setupLocalVideo();
joinChannel();
}
private void endCall() {
removeLocalVideo();
removeRemoteVideo();
leaveChannel();
}
private void removeLocalVideo() {
if (mLocalView != null) {
mLocalContainer.removeView(mLocalView);
}
mLocalView = null;
}
private void showButton(boolean show) {
int visibilty = show ? View.VISIBLE : View.GONE;
mMuteBtn.setVisibility(visibilty);
mSwitchCameraBtn.setVisibility(visibilty);
}
private Boolean checkSelfpermission(String permission) {
if (ContextCompat.checkSelfPermission(this,permission) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, REQUESTED_PERMISSIONS, MainActivity.PERMISSION_REQ_ID);
return false;
}
return true;
}
}
When the audio player app is resumed while playing audio, the SeekBar resets to 0. During audio playback, the SeekBar updates progress. However, when the screen is resumed, the SeekBar starts from the beginning without indicating the player's current position. When you press the pause button and then press the play button, it plays at the current position again. In updateProgress() method, there is long currentPosition = mLastPlaybackState.getPosition(); I think this code doesn't indicate the current position when resumed.
I implemented SeekBar update progress based on the UAMP FullScreenActivity
This is my NowPlayingAcitivy.java:
private PlaybackStateCompat mLastPlaybackState;
private final Handler mHandler = new Handler();
private final Runnable mUpdateProgressTask = new Runnable() {
#Override
public void run() {
updateProgress();
}
};
private final ScheduledExecutorService mExecutorService =
Executors.newSingleThreadScheduledExecutor();
private ScheduledFuture<?> mScheduledFuture;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mNowPlayingBinding = DataBindingUtil.setContentView(
this, R.layout.activity_now_playing);
createMediaBrowserCompat();
mNowPlayingBinding.playingInfo.seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
#Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
mNowPlayingBinding.playingInfo.tvStart.setText(DateUtils.formatElapsedTime(
progress/1000));
}
#Override
public void onStartTrackingTouch(SeekBar seekBar) {
// Cancel the future returned by scheduleAtFixedRate() to stop the SeekBar from progressing
stopSeekbarUpdate();
}
#Override
public void onStopTrackingTouch(SeekBar seekBar) {
MediaControllerCompat.getMediaController(NowPlayingActivity.this)
.getTransportControls().seekTo(seekBar.getProgress());
// Create and execute a periodic action to update the SeekBar progress
scheduleSeekbarUpdate();
}
});
}
private void createMediaBrowserCompat() {
mMediaBrowser = new MediaBrowserCompat(this,
new ComponentName(this, PodcastService.class),
mConnectionCallbacks,
null);
}
#Override
protected void onStart() {
super.onStart();
mMediaBrowser.connect();
}
#Override
protected void onResume() {
super.onResume();
setVolumeControlStream(AudioManager.STREAM_MUSIC);
}
#Override
protected void onStop() {
super.onStop();
if (MediaControllerCompat.getMediaController(this) != null) {
MediaControllerCompat.getMediaController(this).unregisterCallback(controllerCallback);
}
mMediaBrowser.disconnect();
}
#Override
protected void onDestroy() {
super.onDestroy();
stopSeekbarUpdate();
mExecutorService.shutdown();
}
private final MediaBrowserCompat.ConnectionCallback mConnectionCallbacks =
new MediaBrowserCompat.ConnectionCallback() {
#Override
public void onConnected() {
MediaSessionCompat.Token token = mMediaBrowser.getSessionToken();
MediaControllerCompat mediaController = null;
try {
mediaController = new MediaControllerCompat(NowPlayingActivity.this, token);
} catch (RemoteException e) {
Timber.e("Error creating media controller");
}
MediaControllerCompat.setMediaController(NowPlayingActivity.this,
mediaController);
buildTransportControls();
}
#Override
public void onConnectionSuspended() {
super.onConnectionSuspended();
}
#Override
public void onConnectionFailed() {
super.onConnectionFailed();
}
};
void buildTransportControls() {
mNowPlayingBinding.playingInfo.ibPlayPause.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
PlaybackStateCompat pbState =
MediaControllerCompat.getMediaController(NowPlayingActivity.this).getPlaybackState();
if (pbState != null) {
MediaControllerCompat.TransportControls controls =
MediaControllerCompat.getMediaController(NowPlayingActivity.this).getTransportControls();
switch (pbState.getState()) {
case PlaybackStateCompat.STATE_PLAYING: // fall through
case PlaybackStateCompat.STATE_BUFFERING:
controls.pause();
stopSeekbarUpdate();
break;
case PlaybackStateCompat.STATE_PAUSED:
case PlaybackStateCompat.STATE_STOPPED:
controls.play();
scheduleSeekbarUpdate();
break;
default:
Timber.d("onClick with state " + pbState);
}
}
}
});
MediaControllerCompat mediaController =
MediaControllerCompat.getMediaController(NowPlayingActivity.this);
MediaMetadataCompat metadata = mediaController.getMetadata();
PlaybackStateCompat pbState = mediaController.getPlaybackState();
updatePlaybackState(pbState);
if (metadata != null) {
// Get the episode duration from the metadata and sets the end time to the textView
updateDuration(metadata);
}
// Set the current progress to the current position
updateProgress();
if (pbState != null && (pbState.getState() == PlaybackStateCompat.STATE_PLAYING ||
pbState.getState() == PlaybackStateCompat.STATE_BUFFERING)) {
scheduleSeekbarUpdate();
}
mediaController.registerCallback(controllerCallback);
}
MediaControllerCompat.Callback controllerCallback = new MediaControllerCompat.Callback() {
#Override
public void onMetadataChanged(MediaMetadataCompat metadata) {
super.onMetadataChanged(metadata);
if (metadata != null) {
updateDuration(metadata);
}
}
#Override
public void onPlaybackStateChanged(PlaybackStateCompat state) {
super.onPlaybackStateChanged(state);
// Update the playback state
updatePlaybackState(state);
}
};
/**
* Creates and executes a periodic action that becomes enabled first after the given initial delay,
* and subsequently with the given period;that is executions will commence after initialDelay
* then initialDelay + period, then initialDelay + 2 * period, and so on.
*/
private void scheduleSeekbarUpdate() {
stopSeekbarUpdate();
if (!mExecutorService.isShutdown()) {
mScheduleFuture = mExecutorService.scheduleAtFixedRate(
new Runnable() {
#Override
public void run() {
mHandler.post(mUpdateProgressTask);
}
}, 100,
1000, TimeUnit.MILLISECONDS);
}
}
/**
* Cancels the future returned by scheduleAtFixedRate() to stop the SeekBar from progressing.
*/
private void stopSeekbarUpdate() {
if (mScheduledFuture != null) {
mScheduledFuture.cancel(false);
}
}
/**
* Gets the episode duration from the metadata and sets the end time to be displayed in the TextView.
*/
private void updateDuration(MediaMetadataCompat metadata) {
if (metadata == null) {
return;
}
int duration = (int) metadata.getLong(MediaMetadataCompat.METADATA_KEY_DURATION)
* 1000;
mNowPlayingBinding.playingInfo.seekBar.setMax(duration);
mNowPlayingBinding.playingInfo.tvEnd.setText(
DateUtils.formatElapsedTime(duration / 1000));
}
/**
* Calculates the current position (distance = timeDelta * velocity) and sets the current progress
* to the current position.
*/
private void updateProgress() {
if (mLastPlaybackState == null) {
return;
}
long currentPosition = mLastPlaybackState.getPosition();
if (mLastPlaybackState.getState() == PlaybackStateCompat.STATE_PLAYING) {
// Calculate the elapsed time between the last position update and now and unless
// paused, we can assume (delta * speed) + current position is approximately the
// latest position. This ensure that we do not repeatedly call the getPlaybackState()
// on MediaControllerCompat.
long timeDelta = SystemClock.elapsedRealtime() -
mLastPlaybackState.getLastPositionUpdateTime();
currentPosition += (int) timeDelta * mLastPlaybackState.getPlaybackSpeed();
}
mNowPlayingBinding.playingInfo.seekBar.setProgress((int) currentPosition);
}
private void updatePlaybackState(PlaybackStateCompat state) {
if (state == null) {
return;
}
mLastPlaybackState = state;
switch (state.getState()) {
case PlaybackStateCompat.STATE_PLAYING:
hideLoading();
mNowPlayingBinding.playingInfo.ibPlayPause.setImageResource(R.drawable.exo_controls_pause);
scheduleSeekbarUpdate();
break;
case PlaybackStateCompat.STATE_PAUSED:
hideLoading();
mNowPlayingBinding.playingInfo.ibPlayPause.setImageResource(R.drawable.exo_controls_play);
stopSeekbarUpdate();
break;
case PlaybackStateCompat.STATE_NONE:
case PlaybackStateCompat.STATE_STOPPED:
hideLoading();
mNowPlayingBinding.playingInfo.ibPlayPause.setImageResource(R.drawable.exo_controls_play);
stopSeekbarUpdate();
break;
case PlaybackStateCompat.STATE_BUFFERING:
showLoading();
mNowPlayingBinding.playingInfo.ibPlayPause.setImageResource(R.drawable.exo_controls_play);
stopSeekbarUpdate();
break;
default:
Timber.d("Unhandled state " + state.getState());
}
}
This is my PodcastService.java:
public class PodcastService extends MediaBrowserServiceCompat implements Player.EventListener {
#Override
public void onCreate() {
super.onCreate();
initializeMediaSession();
}
private void initializeMediaSession() {
mMediaSession = new MediaSessionCompat(PodcastService.this, TAG);
mMediaSession.setFlags(
MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS |
MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);
mStateBuilder = new PlaybackStateCompat.Builder()
.setActions(
PlaybackStateCompat.ACTION_PLAY |
PlaybackStateCompat.ACTION_PAUSE |
PlaybackStateCompat.ACTION_REWIND |
PlaybackStateCompat.ACTION_FAST_FORWARD |
PlaybackStateCompat.ACTION_PLAY_PAUSE);
mMediaSession.setPlaybackState(mStateBuilder.build());
mMediaSession.setCallback(new MySessionCallback());
setSessionToken(mMediaSession.getSessionToken());
mMediaSession.setSessionActivity(PendingIntent.getActivity(this,
11,
new Intent(this, NowPlayingActivity.class),
PendingIntent.FLAG_UPDATE_CURRENT));
}
private void initializePlayer() {
if (mExoPlayer == null) {
DefaultRenderersFactory defaultRenderersFactory = new DefaultRenderersFactory(this);
TrackSelector trackSelector = new DefaultTrackSelector();
LoadControl loadControl = new DefaultLoadControl();
mExoPlayer = ExoPlayerFactory.newSimpleInstance(this, defaultRenderersFactory,
trackSelector, loadControl);
mExoPlayer.addListener(this);
// Prepare the MediaSource
Uri mediaUri = Uri.parse(mUrl);
MediaSource mediaSource = buildMediaSource(mediaUri);
mExoPlayer.prepare(mediaSource);
mExoPlayer.setPlayWhenReady(true);
}
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent == null || intent.getAction() == null) {
Timber.e("intent in onStartCommand is null");
return START_STICKY;
}
// Check if the old player should be released
if (intent.getAction() != null && intent.getAction().equals(ACTION_RELEASE_OLD_PLAYER)) {
if (mExoPlayer != null) {
mExoPlayer.stop();
releasePlayer();
}
}
Bundle b = intent.getBundleExtra(EXTRA_ITEM);
if (b != null) {
mItem = b.getParcelable(EXTRA_ITEM);
mUrl = mItem.getEnclosures().get(0).getUrl();
}
initializePlayer();
// Convert hh:mm:ss string to seconds to put it into the metadata
long duration = PodUtils.getDurationInMilliSeconds(mItem);
MediaMetadataCompat metadata = new MediaMetadataCompat.Builder()
.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, duration).build();
mMediaSession.setMetadata(metadata);
return START_STICKY;
}
private void releasePlayer() {
mExoPlayer.release();
mExoPlayer = null;
}
#Override
public void onTaskRemoved(Intent rootIntent) {
super.onTaskRemoved(rootIntent);
if (mExoPlayer != null) {
mExoPlayer.stop(true);
}
stopSelf();
}
#Override
public void onDestroy() {
mMediaSession.release();
releasePlayer();
super.onDestroy();
}
private MediaSource buildMediaSource(Uri mediaUri) {
String userAgent = Util.getUserAgent(this, getString(R.string.app_name));
DefaultDataSourceFactory dataSourceFactory = new DefaultDataSourceFactory(
this, userAgent);
CacheDataSourceFactory cacheDataSourceFactory =
new CacheDataSourceFactory(
DownloadUtil.getCache(this),
dataSourceFactory,
CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR);
return new ExtractorMediaSource.Factory(cacheDataSourceFactory).createMediaSource(mediaUri);
}
#Nullable
#Override
public BrowserRoot onGetRoot(#NonNull String clientPackageName, int clientUid,
#Nullable Bundle rootHints) {
return new BrowserRoot("pod_root_id", null);
}
#Override
public void onLoadChildren(#NonNull String parentMediaId,
#NonNull Result<List<MediaBrowserCompat.MediaItem>> result) {
// Browsing not allowed
if (TextUtils.equals("empty_root_id", parentMediaId)) {
result.sendResult(null);
return;
}
List<MediaBrowserCompat.MediaItem> mediaItems = new ArrayList<>();
// Check if this is the root menu:
if ("pod_root_id".equals(parentMediaId)) {
// Build the MediaItem objects for the top level,
// and put them in the mediaItems list...
} else {
// Examine the passed parentMediaId to see which submenu we're at,
// and put the children of that menu in the mediaItems list...
}
result.sendResult(mediaItems);
}
private class MySessionCallback extends MediaSessionCompat.Callback {
#Override
public void onPlay() {
startService(new Intent(getApplicationContext(), PodcastService.class));
mMediaSession.setActive(true);
// Start the player
if (mExoPlayer != null) {
mExoPlayer.setPlayWhenReady(true);
}
}
#Override
public void onPause() {
mExoPlayer.setPlayWhenReady(false);
stopForeground(false);
}
#Override
public void onRewind() {
mExoPlayer.seekTo(Math.max(mExoPlayer.getCurrentPosition() - 10000, 0));
}
#Override
public void onFastForward() {
long duration = mExoPlayer.getDuration();
mExoPlayer.seekTo(Math.min(mExoPlayer.getCurrentPosition() + 30000, duration));
}
#Override
public void onStop() {
stopSelf();
mMediaSession.setActive(false);
mExoPlayer.stop();
stopForeground(true);
}
#Override
public void onSeekTo(long pos) {
super.onSeekTo(pos);
if (mExoPlayer != null) {
mExoPlayer.seekTo((int) pos);
}
}
}
// Player Event Listeners
#Override
public void onTimelineChanged(Timeline timeline, #Nullable Object manifest, int reason) {
}
#Override
public void onPlayerError(ExoPlaybackException error) {
}
#Override
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
if (playbackState == Player.STATE_IDLE) {
mStateBuilder.setState(PlaybackStateCompat.STATE_PAUSED,
mExoPlayer.getCurrentPosition(), 1f);
} else if (playbackState == Player.STATE_BUFFERING) {
mStateBuilder.setState(PlaybackStateCompat.STATE_BUFFERING,
mExoPlayer.getCurrentPosition(), 1f);
} else if (playbackState == Player.STATE_READY && playWhenReady) {
mStateBuilder.setState(PlaybackStateCompat.STATE_PLAYING,
mExoPlayer.getCurrentPosition(), 1f);
Timber.d("onPlayerStateChanged: we are playing");
} else if (playbackState == Player.STATE_READY) {
mStateBuilder.setState(PlaybackStateCompat.STATE_PAUSED,
mExoPlayer.getCurrentPosition(), 1f);
Timber.d("onPlayerStateChanged: we are paused");
} else if (playbackState == Player.STATE_ENDED) {
mStateBuilder.setState(PlaybackStateCompat.STATE_PAUSED,
mExoPlayer.getCurrentPosition(), 1f);
} else {
mStateBuilder.setState(PlaybackStateCompat.STATE_NONE,
mExoPlayer.getCurrentPosition(), 1f);
}
mMediaSession.setPlaybackState(mStateBuilder.build());
}
}
Edit: The full source code is available here.
To set the state progress based on value should use setProgress(value) method.
when paused the music save value from seekBar as an Integer, then when resume it get that value and put it as a parameter in setProgress() method.
when you pause music to save the value:
#Override
protected void onPause() {
super.onPause();
mSeekBarRate.setOnSeekBarChangeListener(
new SeekBar.OnSeekBarChangeListener() {
int progress = 0;
#Override
public void onProgressChanged(SeekBar mSeekBarRate, int progressValue, boolean fromUser) {
progress = progressValue;
}
#Override
public void onStartTrackingTouch(SeekBar mSeekBarRate) {
}
#Override
public void onStopTrackingTouch(SeekBar mSeekBarRate) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(MainActivity.this);
SharedPreferences.Editor editor = prefs.edit();
editor.putInt("value", progress);
}
});
}
when you resume music retrieve that value:
#Override
protected void onStart() {
super.onStart();
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
int value = prefs.getInt("value", 0);
mSeekBarRate.setProgress(value);
}
Hope it Helps you.
I'm making an application that implements the picture in picture functionality in android.
When you click on the image, open a new activity with the picture in picture functionality.
if I activate the function without giving play to the video, the functionality is done correctly, but when activating the picture in picture functionality when the video is playing, when entering picture in picture, the video stops playing.
Image
Custom videoYoutubePlayer
public class VideoYoutubeView extends RelativeLayout {
private final static String TAG = VideoYoutubeView.class.getName();
#BindView(R.id.youtube_player)
YouTubePlayerView youTubePlayerView;
#BindView(R.id.movie_play)
TextView btnPlay;
#BindView(R.id.movie_forward)
TextView btnForward;
#BindView(R.id.movie_back)
TextView btnBack;
#BindView(R.id.text_time_movie)
TextView textTimeVideo;
#BindView(R.id.seek_progress_movie)
SeekBar seekBarMovie;
#BindView(R.id.text_down)
TextView btnDown;
private YouTubePlayer mPlayer;
private Handler mHandler = null;
private OnPipListener onPipListener;
public VideoYoutubeView(Context context) {
super(context);
}
public VideoYoutubeView(Context context, AttributeSet attrs) {
super(context, attrs);
this.init(context);
}
public VideoYoutubeView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.init(context);
}
public void setOnPipListener(#NonNull OnPipListener onPipListener) {
this.onPipListener = onPipListener;
}
public void init(Context context) {
View view = inflate(context, R.layout.youtube_view, this);
ButterKnife.bind(this, view);
this.mHandler = new Handler();
this.setTypeFace(context);
this.setUpVideoView();
}
private void setUpVideoView() {
final OnClickListener listener = view -> {
switch (view.getId()) {
case R.id.movie_play:
toggleMovie();
break;
case R.id.movie_back:
fastReview();
break;
case R.id.movie_forward:
fastForward();
break;
case R.id.text_down:
goToPip();
break;
}
};
this.btnPlay.setOnClickListener(listener);
this.btnForward.setOnClickListener(listener);
this.btnBack.setOnClickListener(listener);
this.btnDown.setOnClickListener(listener);
}
private void goToPip() {
if (this.onPipListener == null)
return;
this.onPipListener.goPiP();
}
public void hideControls() {
this.btnPlay.setVisibility(INVISIBLE);
this.btnDown.setVisibility(GONE);
this.textTimeVideo.setVisibility(GONE);
this.seekBarMovie.setVisibility(GONE);
}
public void showControls() {
this.btnPlay.setVisibility(VISIBLE);
this.btnDown.setVisibility(VISIBLE);
this.textTimeVideo.setVisibility(VISIBLE);
this.seekBarMovie.setVisibility(VISIBLE);
}
private void fastForward() {
}
private void fastReview() {
}
private void toggleMovie() {
if (this.mPlayer == null)
return;
if (this.mPlayer.isPlaying())
moviePause();
else
moviePlay();
}
private void moviePlay() {
if (this.mPlayer == null)
return;
this.mPlayer.play();
adjustStateMovie();
}
private void moviePause() {
if (this.mPlayer == null)
return;
this.mPlayer.pause();
adjustStateMovie();
}
private void adjustStateMovie() {
if (mPlayer == null)
return;
this.btnPlay.setText(mPlayer.isPlaying() ? R.string.iconPlay : R.string.iconPause);
}
private void setTypeFace(Context context) {
this.btnPlay.setTypeface(FontManager.getTypeface(context, FontManager.FONTAWESOME));
this.btnBack.setTypeface(FontManager.getTypeface(context, FontManager.FONTAWESOME));
this.btnForward.setTypeface(FontManager.getTypeface(context, FontManager.FONTAWESOME));
this.btnDown.setTypeface(FontManager.getTypeface(context, FontManager.FONTAWESOME));
}
public void initialize(String apiKey, String videoId) {
this.youTubePlayerView.initialize(apiKey, getOnInitializedListener(videoId));
}
private YouTubePlayer.OnInitializedListener getOnInitializedListener(String videoId) {
return new YouTubePlayer.OnInitializedListener() {
#Override
public void onInitializationSuccess(YouTubePlayer.Provider provider,
YouTubePlayer youTubePlayer, boolean wasRestored) {
if (null == youTubePlayer) return;
mPlayer = youTubePlayer;
currentTimeVideo();
// Start buffering
if (!wasRestored) {
youTubePlayer.cueVideo(videoId);
}
youTubePlayer.setPlayerStyle(YouTubePlayer.PlayerStyle.CHROMELESS);
youTubePlayer.setFullscreenControlFlags(YouTubePlayer.FULLSCREEN_FLAG_CONTROL_ORIENTATION);
// Add listeners to YouTubePlayer instance
youTubePlayer.setPlayerStateChangeListener(new YouTubePlayer.PlayerStateChangeListener() {
#Override
public void onAdStarted() {
}
#Override
public void onError(YouTubePlayer.ErrorReason arg0) {
}
#Override
public void onLoaded(String arg0) {
setSeekValue();
}
#Override
public void onLoading() {
}
#Override
public void onVideoEnded() {
}
#Override
public void onVideoStarted() {
currentTimeVideo();
}
});
youTubePlayer.setPlaybackEventListener(new YouTubePlayer.PlaybackEventListener() {
#Override
public void onBuffering(boolean arg0) {
}
#Override
public void onPaused() {
mHandler.removeCallbacks(runnable);
}
#Override
public void onPlaying() {
mHandler.postDelayed(runnable, 100);
currentTimeVideo();
}
#Override
public void onSeekTo(int arg0) {
mHandler.postDelayed(runnable, 100);
}
#Override
public void onStopped() {
mHandler.removeCallbacks(runnable);
}
});
}
#Override
public void onInitializationFailure(YouTubePlayer.Provider provider, YouTubeInitializationResult youTubeInitializationResult) {
Log.d(TAG, "onInitializationFailure: Error initialize video youtube.");
}
};
}
private void currentTimeVideo() {
if (null == mPlayer) return;
String formattedTime = Util.formatTimeVideo(mPlayer.getDurationMillis() - mPlayer.getCurrentTimeMillis());
textTimeVideo.setText(formattedTime);
}
private void setSeekValue() {
if (mPlayer == null)
return;
this.seekBarMovie.setMax(mPlayer.getDurationMillis());
}
private Runnable runnable = new Runnable() {
#Override
public void run() {
currentTimeVideo();
mHandler.postDelayed(this, 100);
seekBarMovie.setProgress(mPlayer.getCurrentTimeMillis(), true);
}
};
public interface OnPipListener {
void goPiP();
}
}
Activity implement picture in picture functionality:
public class VideoActivity extends YouTubeBaseActivity {
#BindView(R.id.movie_chat)
VideoYoutubeView movieChat;
private final PictureInPictureParams.Builder mPictureInPictureParamsBuilder =
new PictureInPictureParams.Builder();
private VideoYoutubeView.OnPipListener onPipListener = (this::setUpPip);
public static Intent getCallIntent(Context context, String messageConversation) {
Intent intent = new Intent(context, VideoActivity.class);
return intent;
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_video);
ButterKnife.bind(this);
this.setUpView();
}
private void setUpView() {
this.movieChat.initialize("", "JRfuAukYTKg");
this.movieChat.setOnPipListener(onPipListener);
}
public void setUpPip() {
if (movieChat == null)
return;
this.movieChat.hideControls();
//Rational aspectRatio = new Rational(movieChat.getWidth(), movieChat.getHeight());
Rational aspectRatio = new Rational(20, 10);
mPictureInPictureParamsBuilder.setAspectRatio(aspectRatio).build();
enterPictureInPictureMode(mPictureInPictureParamsBuilder.build());
}
#Override
public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode, Configuration newConfig) {
super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig);
if (!isInPictureInPictureMode) {
// Show the video controls if the video is not playing
if (movieChat != null /*&& !movieChat.isPlaying()*/) {
movieChat.showControls();
}
}
}
}
Someone can help me in the implementation of this functionality with the YoutubeAndroidPlayerApi library?
I think you also have to override onPause() in your activity -
#Override
public void onPause() {
// If called while in PIP mode, do not pause playback
if (isInPictureInPictureMode()) {
// Continue playback
...
} else {
// Use existing playback logic for paused Activity behavior.
...
}
}
Referencing Android Documentation for Picture-in-picture
I am using Sinch and Parse for my instant messaging system integrated in our application, and I have two concerns.
1) For some reason, I am receiving the following error when the messaging activity is displayed: The message client did not start". Furthermore, the message does not seem to go through in Sinch and not reflected visually in the application.
Below is the activity code (when the user clicks on the "quick chat" button, it takes them to the messaging activity page.
Below is the activity code for the messaging activity
public class MessagingActivity extends Activity implements ServiceConnection, MessageClientListener {
private String recipientId;
private Button sendButton;
private EditText messageBodyField;
private String messageBody;
private MessageService.MessageServiceInterface messageService;
private MessageAdapter messageAdapter;
private ListView messagesList;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.messaging);
doBind();
messagesList = (ListView) findViewById(R.id.listMessages);
messageAdapter = new MessageAdapter(this);
messagesList.setAdapter(messageAdapter);
Intent intent = getIntent();
recipientId = intent.getStringExtra("Name");
messageBodyField = (EditText) findViewById(R.id.messageBodyField);
sendButton = (Button) findViewById(R.id.sendButton);
sendButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
sendMessage();
}
});
}
private void sendMessage() {
messageBodyField = (EditText) findViewById(R.id.messageBodyField);
messageBody = messageBodyField.getText().toString();
if (messageBody.isEmpty()) {
Toast.makeText(this, "Please enter a message", Toast.LENGTH_LONG).show();
return;
}
//Here is where you will actually send the message throught Sinch
messageService.sendMessage(recipientId, messageBody);
messageBodyField.setText("");
}
private void doBind() {
Intent serviceIntent = new Intent(this, MessageService.class);
bindService(serviceIntent, this, BIND_AUTO_CREATE);
}
#Override
public void onDestroy() {
unbindService(this);
super.onDestroy();
}
#Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
//Define the messaging service and add a listener
messageService = (MessageService.MessageServiceInterface) iBinder;
messageService.addMessageClientListener(this);
if (!messageService.isSinchClientStarted()) {
Toast.makeText(this, "The message client did not start."
,Toast.LENGTH_LONG).show();
}
}
#Override
public void onServiceDisconnected(ComponentName componentName) {
messageService = null;
}
#Override
public void onMessageDelivered(MessageClient client, MessageDeliveryInfo deliveryInfo) {
//Intentionally left blank
}
#Override
public void onMessageFailed(MessageClient client, Message message,
MessageFailureInfo failureInfo) {
//Notify the user if message fails to send
Toast.makeText(this, "Message failed to send.", Toast.LENGTH_LONG).show();
}
#Override
public void onIncomingMessage(MessageClient client, Message message) {
messageAdapter.addMessage(message, MessageAdapter.DIRECTION_INCOMING);
}
#Override
public void onMessageSent(MessageClient client, Message message, String recipientId) {
messageAdapter.addMessage(message, MessageAdapter.DIRECTION_OUTGOING);
}
#Override
public void onShouldSendPushData(MessageClient client, Message message, List<PushPair> pushPairs) {
//Intentionally left blank
}
}
I have verified that the APP_KEY, the APP_SECRET and ENVIRONMENT matches what was recorded on Sinch.
I have tried this both on the emulator, and on a physical device.
Thanks in advance
Code service
public class MessageService extends Service implements SinchClientListener {
private static final String APP_KEY = "XXXXX";
private static final String APP_SECRET = "YYYYY";
private static final String ENVIRONMENT = "sandbox.sinch.com";
private final MessageServiceInterface serviceInterface = new MessageServiceInterface();
private SinchClient sinchClient = null;
private MessageClient messageClient = null;
private String currentUserId;
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
currentUserId = ParseUser.getCurrentUser().getObjectId().toString();
if (currentUserId != null && !isSinchClientStarted()) {
startSinchClient(currentUserId);
}
return super.onStartCommand(intent, flags, startId);
}
public void startSinchClient(String username) {
sinchClient = Sinch.getSinchClientBuilder().context(this).userId(username).applicationKey(APP_KEY)
.applicationSecret(APP_SECRET).environmentHost(ENVIRONMENT).build();
sinchClient.addSinchClientListener(this);
sinchClient.setSupportMessaging(true);
sinchClient.setSupportActiveConnectionInBackground(true);
sinchClient.checkManifest();
sinchClient.start();
}
private boolean isSinchClientStarted() {
return sinchClient != null && sinchClient.isStarted();
}
#Override
public void onClientFailed(SinchClient client, SinchError error) {
sinchClient = null;
}
#Override
public void onClientStarted(SinchClient client) {
client.startListeningOnActiveConnection();
messageClient = client.getMessageClient();
}
#Override
public void onClientStopped(SinchClient client) {
sinchClient = null;
}
public void stop() {
if (isSinchClientStarted()) {
sinchClient.stop();
sinchClient.removeSinchClientListener(this);
}
sinchClient = null;
}
#Override
public IBinder onBind(Intent intent) {
return serviceInterface;
}
#Override
public void onLogMessage(int level, String area, String message) {
//Intentionally left blank
}
#Override
public void onRegistrationCredentialsRequired(SinchClient client, ClientRegistration clientRegistration) {
//Intentionally left blank
}
public void sendMessage(String recipientUserId, String textBody) {
if (messageClient != null) {
WritableMessage message = new WritableMessage(recipientUserId, textBody);
messageClient.send(message);
}
}
public void addMessageClientListener(MessageClientListener listener) {
if (messageClient != null) {
messageClient.addMessageClientListener(listener);
}
}
public void removeMessageClientListener(MessageClientListener listener) {
if (messageClient != null) {
messageClient.removeMessageClientListener(listener);
}
}
public class MessageServiceInterface extends Binder {
public void sendMessage(String recipientUserId, String textBody) {
MessageService.this.sendMessage(recipientUserId, textBody);
}
public void addMessageClientListener(MessageClientListener listener) {
MessageService.this.addMessageClientListener(listener);
}
public void removeMessageClientListener(MessageClientListener listener) {
MessageService.this.removeMessageClientListener(listener);
}
public boolean isSinchClientStarted() {
return MessageService.this.isSinchClientStarted();
}
}
}
2) Limit: I would like to set a limit, where a only 25 messages would be available between a particular party.
Thanks in advance, and if you need any clarification, let me know.
Update 3
When user clicks on this button, he is taking to the MessagingActivity with the person he has been matched to based upon the below criteria
final Button ibutton = (Button) this.findViewById(R.id.btnQuickChat);
idrinks.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
openConversation();
}
private void openConversation() {
// TODO Auto-generated method stub
ParseQuery<ParseUser> query = ParseUser.getQuery();
query.whereNotEqualTo("objectId", ParseUser.getCurrentUser()
.getObjectId());
query.setLimit(1);
query.findInBackground(new FindCallback<ParseUser>() {
public void done(List<ParseUser> user, ParseException e) {
if (e == null) {
Intent intent = new Intent(getApplicationContext(), MessagingActivity.class);
intent.putExtra("RECIPIENT_ID", user.get(0).getObjectId());
startActivity(intent);
} else {
Toast.makeText(getApplicationContext(),
"Error finding that user",
Toast.LENGTH_SHORT).show();
}
}
});
}
});
MessagingActivity (nearly same as one provided in tutorial):
public class MessagingActivity extends Activity {
private String recipientId;
private EditText messageBodyField;
private String messageBody;
private MessageService.MessageServiceInterface messageService;
private MessageAdapter messageAdapter;
private ListView messagesList;
private String currentUserId;
private ServiceConnection serviceConnection = new MyServiceConnection();
private MessageClientListener messageClientListener = new MyMessageClientListener();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.messaging);
bindService(new Intent(this, MessageService.class), serviceConnection, BIND_AUTO_CREATE);
Intent intent = getIntent();
recipientId = intent.getStringExtra("RECIPIENT_ID");
currentUserId = ParseUser.getCurrentUser().getObjectId();
messagesList = (ListView) findViewById(R.id.listMessages);
messageAdapter = new MessageAdapter(this);
messagesList.setAdapter(messageAdapter);
populateMessageHistory();
messageBodyField = (EditText) findViewById(R.id.messageBodyField);
findViewById(R.id.sendButton).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
sendMessage();
}
});
}
//get previous messages from parse & display
private void populateMessageHistory() {
String[] userIds = {currentUserId, recipientId};
ParseQuery<ParseObject> query = ParseQuery.getQuery("ParseMessage");
query.whereContainedIn("senderId", Arrays.asList(userIds));
query.whereContainedIn("recipientId", Arrays.asList(userIds));
query.orderByAscending("createdAt");
query.findInBackground(new FindCallback<ParseObject>() {
#Override
public void done(List<ParseObject> messageList, com.parse.ParseException e) {
if (e == null) {
for (int i = 0; i < messageList.size(); i++) {
WritableMessage message = new WritableMessage(messageList.get(i).get("recipientId").toString(), messageList.get(i).get("messageText").toString());
if (messageList.get(i).get("senderId").toString().equals(currentUserId)) {
messageAdapter.addMessage(message, MessageAdapter.DIRECTION_OUTGOING);
} else {
messageAdapter.addMessage(message, MessageAdapter.DIRECTION_INCOMING);
}
}
}
}
});
}
private void sendMessage() {
messageBody = messageBodyField.getText().toString();
if (messageBody.isEmpty()) {
Toast.makeText(this, "Please enter a message", Toast.LENGTH_LONG).show();
return;
}
messageService.sendMessage(recipientId, messageBody);
messageBodyField.setText("");
}
#Override
public void onDestroy() {
messageService.removeMessageClientListener(messageClientListener);
unbindService(serviceConnection);
super.onDestroy();
}
private class MyServiceConnection implements ServiceConnection {
#Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
messageService = (MessageService.MessageServiceInterface) iBinder;
messageService.addMessageClientListener(messageClientListener);
}
#Override
public void onServiceDisconnected(ComponentName componentName) {
messageService = null;
}
}
private class MyMessageClientListener implements MessageClientListener {
#Override
public void onMessageFailed(MessageClient client, Message message,
MessageFailureInfo failureInfo) {
Toast.makeText(MessagingActivity.this, "Message failed to send.", Toast.LENGTH_LONG).show();
}
#Override
public void onIncomingMessage(MessageClient client, Message message) {
if (message.getSenderId().equals(recipientId)) {
WritableMessage writableMessage = new WritableMessage(message.getRecipientIds().get(0), message.getTextBody());
messageAdapter.addMessage(writableMessage, MessageAdapter.DIRECTION_INCOMING);
}
}
#Override
public void onMessageSent(MessageClient client, Message message, String recipientId) {
final WritableMessage writableMessage = new WritableMessage(message.getRecipientIds().get(0), message.getTextBody());
//only add message to parse database if it doesn't already exist there
ParseQuery<ParseObject> query = ParseQuery.getQuery("ParseMessage");
query.whereEqualTo("sinchId", message.getMessageId());
query.findInBackground(new FindCallback<ParseObject>() {
#Override
public void done(List<ParseObject> messageList, com.parse.ParseException e) {
if (e == null) {
if (messageList.size() == 0) {
ParseObject parseMessage = new ParseObject("ParseMessage");
parseMessage.put("senderId", currentUserId);
parseMessage.put("recipientId", writableMessage.getRecipientIds().get(0));
parseMessage.put("messageText", writableMessage.getTextBody());
parseMessage.put("sinchId", writableMessage.getMessageId());
parseMessage.saveInBackground();
messageAdapter.addMessage(writableMessage, MessageAdapter.DIRECTION_OUTGOING);
}
}
}
});
}
#Override
public void onMessageDelivered(MessageClient client, MessageDeliveryInfo deliveryInfo) {}
#Override
public void onShouldSendPushData(MessageClient client, Message message, List<PushPair> pushPairs) {}
}
}
MessageService activity
public class MessageService extends Service implements SinchClientListener {
private static final String APP_KEY = "61b1bfc0-b82a-44f5-ab68-dedca69ead8c";
private static final String APP_SECRET = "jrFrLr8Adkm0Na4nLdASDw==";
private static final String ENVIRONMENT = "sandbox.sinch.com";
private final MessageServiceInterface serviceInterface = new MessageServiceInterface();
private SinchClient sinchClient = null;
private MessageClient messageClient = null;
private String currentUserId;
private LocalBroadcastManager broadcaster;
private Intent broadcastIntent = new Intent("com.dooba.beta.matchOptionActivity");
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
currentUserId = ParseUser.getCurrentUser().getObjectId();
if (currentUserId != null && !isSinchClientStarted()) {
startSinchClient(currentUserId);
}
broadcaster = LocalBroadcastManager.getInstance(this);
return super.onStartCommand(intent, flags, startId);
}
public void startSinchClient(String username) {
sinchClient = Sinch.getSinchClientBuilder().context(this).userId(username).applicationKey(APP_KEY)
.applicationSecret(APP_SECRET).environmentHost(ENVIRONMENT).build();
sinchClient.addSinchClientListener(this);
sinchClient.setSupportMessaging(true);
sinchClient.setSupportActiveConnectionInBackground(true);
sinchClient.checkManifest();
sinchClient.start();
}
private boolean isSinchClientStarted() {
return sinchClient != null && sinchClient.isStarted();
}
#Override
public void onClientFailed(SinchClient client, SinchError error) {
broadcastIntent.putExtra("success", false);
broadcaster.sendBroadcast(broadcastIntent);
sinchClient = null;
}
#Override
public void onClientStarted(SinchClient client) {
broadcastIntent.putExtra("success", true);
broadcaster.sendBroadcast(broadcastIntent);
client.startListeningOnActiveConnection();
messageClient = client.getMessageClient();
}
#Override
public void onClientStopped(SinchClient client) {
sinchClient = null;
}
#Override
public IBinder onBind(Intent intent) {
return serviceInterface;
}
#Override
public void onLogMessage(int level, String area, String message) {
}
#Override
public void onRegistrationCredentialsRequired(SinchClient client, ClientRegistration clientRegistration) {
}
public void sendMessage(String recipientUserId, String textBody) {
if (messageClient != null) {
WritableMessage message = new WritableMessage(recipientUserId, textBody);
messageClient.send(message);
}
}
public void addMessageClientListener(MessageClientListener listener) {
if (messageClient != null) {
messageClient.addMessageClientListener(listener);
}
}
public void removeMessageClientListener(MessageClientListener listener) {
if (messageClient != null) {
messageClient.removeMessageClientListener(listener);
}
}
public class MessageServiceInterface extends Binder {
public void sendMessage(String recipientUserId, String textBody) {
MessageService.this.sendMessage(recipientUserId, textBody);
}
public void addMessageClientListener(MessageClientListener listener) {
MessageService.this.addMessageClientListener(listener);
}
public void removeMessageClientListener(MessageClientListener listener) {
MessageService.this.removeMessageClientListener(listener);
}
public boolean isSinchClientStarted() {
return MessageService.this.isSinchClientStarted();
}
}
Message adapter activity:
public class MessageAdapter extends BaseAdapter {
public static final int DIRECTION_INCOMING = 0;
public static final int DIRECTION_OUTGOING = 1;
private List<Pair<WritableMessage, Integer>> messages;
private LayoutInflater layoutInflater;
public MessageAdapter(Activity activity) {
layoutInflater = activity.getLayoutInflater();
messages = new ArrayList<Pair<WritableMessage, Integer>>();
}
public void addMessage(WritableMessage message, int direction) {
messages.add(new Pair(message, direction));
notifyDataSetChanged();
}
#Override
public int getCount() {
return messages.size();
}
#Override
public Object getItem(int i) {
return messages.get(i);
}
#Override
public long getItemId(int i) {
return i;
}
#Override
public int getViewTypeCount() {
return 2;
}
#Override
public int getItemViewType(int i) {
return messages.get(i).second;
}
#Override
public View getView(int i, View convertView, ViewGroup viewGroup) {
int direction = getItemViewType(i);
//show message on left or right, depending on if
//it's incoming or outgoing
if (convertView == null) {
int res = 0;
if (direction == DIRECTION_INCOMING) {
res = R.layout.message_right;
} else if (direction == DIRECTION_OUTGOING) {
res = R.layout.message_left;
}
convertView = layoutInflater.inflate(res, viewGroup, false);
}
WritableMessage message = messages.get(i).first;
TextView txtMessage = (TextView) convertView.findViewById(R.id.txtMessage);
txtMessage.setText(message.getTextBody());
return convertView;
}
}
I wrote the tutorial you're following, and actually just updated it yesterday to fix your first problem. Check out http://tutorial.sinch.com/android-messaging-tutorial/#show-spinner to see the updates. Instead of showing a toast message when the service is not started, the new version will show a progress dialog (loading spinner) until the service has either started or failed to start.
Could you clarify your second question?
Just declare your service in manifest. I had the same problem and it solved it.