I am using FirestorePagingAdapter for my RecyclerView.
when fragment close I try to stop exoplayer in onStop, onPasue, onDestroy method but
it not work properly, It stop only last video in Recycler view but I didn't understand
what is the main problem here.
#Override
public void onStop() {
super.onStop();
adapter.stopListening();
if (HomeVideoHolder.simpleExoPlayer != null) {
HomeVideoHolder.simpleExoPlayer.stop();
}
}
#Override
public void onDestroy() {
super.onDestroy();
adapter.stopListening();
if (HomeVideoHolder.simpleExoPlayer != null) {
HomeVideoHolder.simpleExoPlayer.stop();
}
}
#Override
public void onPause() {
super.onPause();
adapter.stopListening();
if (HomeVideoHolder.simpleExoPlayer != null) {
HomeVideoHolder.simpleExoPlayer.stop();
}
I declared simpleExoplayer as a public static in HomeVideoHolder.
public static PlayerView videoViewpath;
public static SimpleExoPlayer simpleExoPlayer;
I also try stop, release, setPlayWhenReady(false) and seek to end of the video
to stop exoplayer.
but every method gave same result for me. They only stop last video of recyclerView.
please give me a solution for this problem......
my Adapter code...
adapter=new FirestorePagingAdapter<HomeClass, HomeVideoHolder>(options) {
#Override
protected void onBindViewHolder(#NonNull HomeVideoHolder holder, int position, #NonNull HomeClass model) {
holder.setVideoView(getActivity(),model.getAd(),model.getpId(),model.getcUid(),model.getUid(),model.getPic(),model.getVideo(),model.getcT());
}
#NonNull
#Override
public HomeVideoHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
return new HomeVideoHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.home_video_item,parent,false));
}
#Override
protected void onLoadingStateChanged(#NonNull LoadingState state) {
switch (state) {
case LOADING_INITIAL:
// The initial load has
swipeRefreshLayout.setRefreshing(true);
break;
// ...
case LOADING_MORE:
// The adapter has started to load an additional page
vLoadMore.setVisibility(View.VISIBLE);
break;
// ...
case LOADED:
// The previous load (either initial or additional) completed
swipeRefreshLayout.setRefreshing(false);
vLoadMore.setVisibility(View.INVISIBLE);
break;
// ...
case FINISHED:
vLoadMore.setVisibility(View.INVISIBLE);
break;
case ERROR:
// The previous load (either initial or additional) failed. Call
adapter.retry();
swipeRefreshLayout.setRefreshing(true);
// the retry() method in order to retry the load operation.
break;
// ...
}
}
};
my viewHolder code....
public void setVideoView(Activity activity,String ad, String pId, String cUid, String uid, String pic, String video, String cT) {
final CircleImageView chanelProfile=view.findViewById(R.id.cPicH);
final TextView chanelName=view.findViewById(R.id.cNameH);
final TextView videoCaption=view.findViewById(R.id.vDesH);
final ImageView videoThreeDot=view.findViewById(R.id.threeDotHome);
Pkey=pId;
user=FirebaseAuth.getInstance().getCurrentUser().getUid();
videoCaption.setText(cT);
setPlayer(video);
}
private void setPlayer(String video) {
if (playCode.equals("true")) {
LoadControl loadControl = new DefaultLoadControl();
BandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
TrackSelector trackSelector = new DefaultTrackSelector(
new AdaptiveTrackSelection.Factory(bandwidthMeter)
);
simpleExoPlayer = ExoPlayerFactory.newSimpleInstance(
view.getContext(), trackSelector, loadControl
);
DefaultHttpDataSourceFactory factory = new DefaultHttpDataSourceFactory(
"Video"
);
ExtractorsFactory extractorsFactory = new DefaultExtractorsFactory();
MediaSource mediaSource = new ExtractorMediaSource(Uri.parse(Video),
factory, extractorsFactory, null, null
);
videoViewpath.setPlayer(simpleExoPlayer);
videoViewpath.setKeepScreenOn(true);
simpleExoPlayer.prepare(mediaSource);
simpleExoPlayer.setPlayWhenReady(false);
simpleExoPlayer.addListener(new Player.DefaultEventListener() {
#Override
public void onTimelineChanged(Timeline timeline, Object manifest, int reason) {
super.onTimelineChanged(timeline, manifest, reason);
}
#Override
public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {
super.onTracksChanged(trackGroups, trackSelections);
}
#Override
public void onLoadingChanged(boolean isLoading) {
super.onLoadingChanged(isLoading);
}
#Override
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
super.onPlayerStateChanged(playWhenReady, playbackState);
switch (playbackState) {
case Player.STATE_BUFFERING:
progressBar.setVisibility(View.VISIBLE);
break;
case Player.STATE_ENDED:
break;
case Player.STATE_IDLE:
break;
case Player.STATE_READY:
addViwes();
progressBar.setVisibility(View.INVISIBLE);
break;
default:
break;
}
}
#Override
public void onRepeatModeChanged(int repeatMode) {
super.onRepeatModeChanged(repeatMode);
}
#Override
public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) {
super.onShuffleModeEnabledChanged(shuffleModeEnabled);
}
#Override
public void onPlayerError(ExoPlaybackException error) {
super.onPlayerError(error);
// simpleExoPlayer.prepare(mediaSource);
// simpleExoPlayer.setPlayWhenReady(false);
}
#Override
public void onPositionDiscontinuity(int reason) {
super.onPositionDiscontinuity(reason);
}
#Override
public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {
super.onPlaybackParametersChanged(playbackParameters);
}
#Override
public void onSeekProcessed() {
super.onSeekProcessed();
}
});
}else {
simpleExoPlayer.stop();
simpleExoPlayer.release();
simpleExoPlayer.clearVideoSurface();
videoViewpath.setPlayer(null);
}
}
don't declare PlayerView and SimpleExoPlayer as static, this is wrong approach... you are keeping reference only to last video player instance and you want all of them to pause/stop all. every HomeViewHolder should keep only own player reference, static field keeps only one instance across all instances of reference-keeping class ("owner", HomeViewHolder)
remove all your static player stopping code from onPause, onStop and onDestroy, player instance (non-static) won't be accessible anyway
override onViewDetachedFromWindow(HomeVideoHolder holder) and/or onViewRecycled(HomeVideoHolder holder) method(s) in adapter, in this place pause/stop this one player instance attached to single HomeVideoHolder
let adapter stop every player when single item gets recycled/detached, not only when destroying Activity/Fragment. currently you have probably some memory leaks, as started players may stay in memory playing some video and you don't have reference to it (only to last one, static field as above) for releasing resources... when you destroy RecyclerView then adapter attached to it will recycle/destroy all currently existing HomeViewPagers, to be shure you may recyclerView.setAdapter(null) in onDestroy (before super call). also check how this pattern (recycling views) works with some Log calls in all overriden-able methods of RecyclerView.Adapter starting with on... (e.g. like mentioned in 3.)
You should be keeping ONE instance of ExoPlayer. Create it in Activity and then pass to adapter through the constuctor. Here is my code:
Adapter:
class VideoAdapter( private val mContext: Context, val exoPlayer: ExoPlayer, options: FirestoreRecyclerOptions<VideoModel?>) :[...]
exoPlayer
.also { exoPlayer ->
styledPlayerView.player = exoPlayer
exoPlayer.seekTo(currentWindow, playbackPosition)
styledPlayerView.setShowBuffering(StyledPlayerView.SHOW_BUFFERING_WHEN_PLAYING)
styledPlayerView.useController = false
exoPlayer.repeatMode = Player.REPEAT_MODE_ONE
}
Activity:
private lateinit var exoPlayer: ExoPlayer [...]
exoPlayer = ExoPlayer.Builder(requireContext())
.build() [...]
videoAdapter = VideoAdapter(requireContext(), exoPlayer, options)
then in my Activities onStop() method:
override fun onStop() {
super.onStop()
videoAdapter.stopListening()
exoPlayer.release()
}
Related
I want to be able to click on an info button in my recyclerview "planner" so I can see my (firestore) participants inside that particular event/day.
But I'm getting an error I cannot fix.
The biggest trouble I'm having is the fact im working within fragments. So copying Youtube doenst always work. + I'm new to coding and java.
This want to make it non static
but when I fix that:
This wants to make it static again
Inside LesAdapter
public LesViewHolder(#NonNull View itemView){
super(itemView);
Lijst_Soort = itemView.findViewById(R.id.idLes);
Lijst_Waar = itemView.findViewById(R.id.idWaar);
Lijst_Wanneer = itemView.findViewById(R.id.idWanneer);
btnInfo = itemView.findViewById(R.id.btnInfo);
btnInfo.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
int position = getAdapterPosition();
if(position !=RecyclerView.NO_POSITION && listener !=null){
listener.onItemClick(getSnapshots().getSnapshot(position), position );
}
}
});
}
public interface OnItemClickListener{
void onItemClick(DocumentSnapshot documentSnapshot, int position);
}
public void setOnItemClickListener(OnItemClickListener listener){
this.listener = listener;
}
Inside HomeFragment could using LesAdapter before .setOnItemClickListener be wrong?
LesAdapter.setOnItemClickListener(new LesAdapter.OnItemClickListener() {
#Override
public void onItemClick(DocumentSnapshot documentSnapshot, int position) {
String wanneer = documentSnapshot.getString("Wanneer");
Intent intent = new Intent(getActivity(), Deelnamelijst.class);
intent.putExtra("Welke les", wanneer);
startActivity(intent);
}
});
This is what i was trying:
https://www.youtube.com/watch?v=3WR4QAiVuCw
https://www.youtube.com/watch?v=ZXoGG2XTjzU
The error is that trying to call LesAdapter.setOnItemClickListener assumes that the setOnItemClickListener method is a static method and hence you can call it without creating an instance.
For your example, setOnItemClickListener is not a static method and hence to call it, you need an instance of LesAdapter. So use whatever instance you creating like
LesAdapter lesAdapter = new LesAdapter();
lesAdapter.setOnItemClickListener(new LesAdapter.OnItemClickListener() {
#Override
public void onItemClick(DocumentSnapshot documentSnapshot, int position) {
String wanneer = documentSnapshot.getString("Wanneer");
Intent intent = new Intent(getActivity(), Deelnamelijst.class);
intent.putExtra("Welke les", wanneer);
startActivity(intent);
}
});
I have a FloatingSearchView in my app to perform some query on my Firestore database. When I check the size of each query, the result is as expected but my view is not updating with the result. I don't understand if this is the queries or if this is how I handle the different adapter.
I have one FirestoreRecyclerAdapter for each query. I don't understand what's wrong. Thank you for your help!
floatingSearchView.setOnSearchListener(new
FloatingSearchView.OnSearchListener() {
#Override
public void onSuggestionClicked(SearchSuggestion
searchSuggestion) {
mLastQuery = searchSuggestion.getBody();
com.google.firebase.firestore.Query qSuggestion =
db
.collection("article")
.whereEqualTo("category", category_key)
.whereEqualTo("type", mLastQuery);
qSuggestion
.get()
.addOnSuccessListener(new
OnSuccessListener<QuerySnapshot>() {
#Override
public void onSuccess(QuerySnapshot
documentSnapshots) {
int size = documentSnapshots
.getDocuments()
.size();
Toast.makeText(Blog.this, "size " + size,
Toast.LENGTH_LONG).show();
}
});
FirestoreRecyclerOptions<Blog_model> opt = new FirestoreRecyclerOptions.Builder<Blog_model>()
.setQuery(qSuggestion, Blog_model.class)
.build();
Log.d("option", opt.getSnapshots().toString());
suggestionAdapter = new FirestoreRecyclerAdapter<Blog_model, Blog.BlogViewHolder>(opt) {
#Override
public void onBindViewHolder(#NonNull Blog.BlogViewHolder holder, int position, #NonNull final Blog_model model) {
holder.setTitle(model.getTitle());
holder.setDesc(model.getDesc());
holder.setImage(getApplicationContext(), model.getPicture());
final String post_key = getSnapshots().getSnapshot(position).getId();
holder.mView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// Ordinary Intent for launching a new activity
final Intent intent = new Intent(Blog.this, BlogDetails.class);
intent.putExtra("article_id", post_key);
intent.putExtra("category_key", category_key);
intent.putExtra("image", model.getPicture());
intent.putExtra("title", model.getTitle());
startActivity(intent);
}
});
}
#Override
public Blog.BlogViewHolder onCreateViewHolder(ViewGroup group, int i) {
// Create a new instance of the ViewHolder, in this case we are using a custom
// layout called R.layout.message for each item
View view = LayoutInflater.from(group.getContext())
.inflate(R.layout.blog_row, group, false);
return new Blog.BlogViewHolder(view);
}
};
floatingSearchView.clearSearchFocus();
mBlogList.setAdapter(suggestionAdapter);
}
#Override
public void onSearchAction(String currentQuery) {
mLastQuery = currentQuery;
// query to firebase
com.google.firebase.firestore.Query qSuggestion =
db
.collection("article")
.whereEqualTo("keyword."+mLastQuery, true);
FirestoreRecyclerOptions<Blog_model> options1 = new FirestoreRecyclerOptions.Builder<Blog_model>()
.setQuery(qSuggestion, Blog_model.class)
.build();
Log.d("option", options1.getSnapshots().toString());
searchAdapter = new FirestoreRecyclerAdapter<Blog_model, Blog.BlogViewHolder>(options1) {
#Override
public void onBindViewHolder(#NonNull Blog.BlogViewHolder holder, int position, #NonNull final Blog_model model) {
holder.setTitle(model.getTitle());
holder.setDesc(model.getDesc());
holder.setImage(getApplicationContext(), model.getPicture());
final String post_key = getSnapshots().getSnapshot(position).getId();
holder.mView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// Toast.makeText(Blog.this, "title", Toast.LENGTH_LONG).show();
// Ordinary Intent for launching a new activity
final Intent intent = new Intent(Blog.this, BlogDetails.class);
intent.putExtra("article_id", post_key);
intent.putExtra("category_key", category_key);
intent.putExtra("image", model.getPicture());
intent.putExtra("title", model.getTitle());
startActivity(intent);
}
});
}
#Override
public Blog.BlogViewHolder onCreateViewHolder(ViewGroup group, int i) {
// Create a new instance of the ViewHolder, in this case we are using a custom
// layout called R.layout.message for each item
View view = LayoutInflater.from(group.getContext())
.inflate(R.layout.blog_row, group, false);
return new Blog.BlogViewHolder(view);
}
};
mBlogList.setAdapter(searchAdapter);
}
});
#Override
public void onStart() {
super.onStart();
adapter.startListening();
if(suggestionAdapter != null){
suggestionAdapter.startListening();
}
if(searchAdapter != null){
searchAdapter.startListening();
}
}
#Override
public void onStop() {
super.onStop();
adapter.stopListening();
if(suggestionAdapter != null){
suggestionAdapter.stopListening();
}
if(searchAdapter != null){
searchAdapter.stopListening();
}
}
You are using two FirestoreRecyclerAdapter objects, which is correct, but the problem in your code is that you are not listening to the second adapter for changes in the right place. To solve this, add inside onSearchAction method:
searchAdapter.startListening();
Right after you create the adapter object. This means that for every character that you type in your FloatingSearchView, you create a new adapter and you populate it with the results that are coming from the database. If you are starting listening in the onStart method, it doesn't help you at all.
On my onBindViewHolder I have this to set the setImageResource
holder.card_image.setImageResource(image);
But my items can be purchased so, I have this to purchase on my holder.view.setOnClickListener()
bp.purchase((Activity) mContext,model.getProduct_id());
so, it goes to this method :
bp = new BillingProcessor() new BillingProcessor.IBillingHandler() {
#Override
public void onProductPurchased(#NonNull String productId, #Nullable TransactionDetails details) {
showToast("onProductPurchased: " + productId);
//Purchased OK
//WANT TO CHANGE THE IMAGE ONCE PURCHASE IS OK
}
#Override
public void onBillingError(int errorCode, #Nullable Throwable error) {
showToast("onBillingError: " + Integer.toString(errorCode));
}
#Override
public void onBillingInitialized() {
showToast("onBillingInitialized");
readyToPurchase = true;
}
#Override
public void onPurchaseHistoryRestored() {
showToast("onPurchaseHistoryRestored");
for(String sku : bp.listOwnedProducts())
Log.d("skuProducts", "Owned Managed Product: " + sku);
for(String sku : bp.listOwnedSubscriptions())
Log.d("skuProducts", "Owned Subscription: " + sku);
}
});
How do I change it if I'm not onBindViewHolder?
My adapter looks like :
FirebaseRecyclerAdapter adapter = new FirebaseRecyclerAdapter< CardPOJO, CardHolder>(options) {
#Override
public CardHolder onCreateViewHolder(ViewGroup parent, int viewType) {
//inflate the single recycler view layout(item)
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.card_product, parent, false);
int width = parent.getMeasuredWidth() / 2;
width -= mContext.getResources().getDimensionPixelSize(R.dimen._8sdp);
final CardHolder cardViewHolder = new CardHolder(view,width);
return cardViewHolder;
}
#Override
public void onDataChanged() {
super.onDataChanged();
tv.setVisibility(getItemCount() == 0 ? View.VISIBLE : View.GONE);
}
#Override
protected void onBindViewHolder(CardHolder holder, int position, final CardPOJO model) {
holder.state.setText(model.getState());
holder.cardName.setText(model.getName());
switch (model.getState()){
case "free":
//Img free
break;
case "not_free":
//Img not free
break;
default:
break;
}
holder.view.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(model.getState().equals("free")){
//stuff
}
else{
//stuff
}
root_ref.child("PurchasedProducts").child(currentuser).addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
bp.purchase((Activity) mContext,model.getProduct_id()); //HERE I CALL THE PURCHASE SO IF IT'S OK I WANT TO DO SOMETHING LIKE holder.card_image.setImageResource(image);
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
}
}
});
}
};
adapter.startListening();
products_recycler.setAdapter(adapter);
If I assume correctly you want to change the view appearance or some image change if some payment is done successful or failed.
for that, you can have a callback which will give you the item position in activity or fragment back from there you can make a server call to make the purchase happen and if everything goes well.
when you make your adapter constructor pass the callback
final SomeAdapter obj = new SomeAdapter(this,new Callback(){
#Override
onPaymentRequested(int position, View view){
//this will get called when you press click on image in bindviewholder
bp = new BillingProcessor() new BillingProcessor.IBillingHandler() {
#Override
public void onProductPurchased(#NonNull String productId, #Nullable TransactionDetails details) {
showToast("onProductPurchased: " + productId);
//Purchased OK
adapterModelList.get(position).setPayment(true);
obj.notifyDataSetChanged();
}
#Override
public void onBillingError(int errorCode, #Nullable Throwable error) {
showToast("onBillingError: " + Integer.toString(errorCode));
}
#Override
public void onBillingInitialized() {
showToast("onBillingInitialized");
readyToPurchase = true;
}
#Override
public void onPurchaseHistoryRestored() {
showToast("onPurchaseHistoryRestored");
for(String sku : bp.listOwnedProducts())
Log.d("skuProducts", "Owned Managed Product: " + sku);
for(String sku : bp.listOwnedSubscriptions())
Log.d("skuProducts", "Owned Subscription: " + sku);
}
});
}
});
recyclerView.setAdapter(obj);
so when you call your obj.notifyDataSetChanged(); it will make the adapter to draw all views again where you can set some flag according to int position recieved for click callback and make it change accordingly.
Edit=>07/12/2018: Tried the Firebase Adapter and made few changes since the code was not enough to replicate the scenario but I have made a sample class made few changes but the basic idea is like below.
1: When user click on view in onBindViewHolder we receive a callback which gives a position parameter in fragment or activity from where we are calling
2: Now we process the payment and when we are done we make a change in Database firebase also by updating the CardPojo to server for that particular user item.
3: while we update the CardPojo on server we also set a flag in card pojo which is a boolean for paymentSuccess which will be true when payment is done.
4: since our payment is done and is synced with server with new flag data now we can just call firebaseRecycler.notifyItemChanged(position); which will get the lates update from the server for that particular position which we have received on callback.
5: Now populateViewHolder() gives you a cardpojo object you can check if payment is done then you can change the image
so here is the sample code involved I have tried to match the scenario at best, hope you understand what I am trying to do here.
so first create a listener or a callback
public interface CallBackInterface {
void onClick(int position,CardPOJO cardPOJO);
}
now instead of initializing the FirebaseRecyclerAdapter in activity or fragment just create a class and extend it this separates your ui logic and gives us the extensibility of doing extra things like adding callback.
public class FirebaseRecycler extends FirebaseRecyclerAdapter<CardPOJO,CardHolder> {
CallBackInterface callBackInterface;
public FirebaseRecycler(Class<CardPOJO> modelClass, int modelLayout, Class<CardHolder> viewHolderClass, DatabaseReference ref) {
super(modelClass, modelLayout, viewHolderClass, ref);
this.callBackInterface = callBackInterface;
}
public FirebaseRecycler(Class<CardPOJO> modelClass, int modelLayout, Class<CardHolder> viewHolderClass, Query ref) {
super(modelClass, modelLayout, viewHolderClass, ref);
this.callBackInterface = callBackInterface;
}
#Override
public CardHolder onCreateViewHolder(ViewGroup parent, int viewType) {
//your inflater logic goes here
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.card_product, parent, false);
CardHolder cardHolder = new CardHolder(view);
return cardHolder;
}
#Override
protected void populateViewHolder(CardHolder viewHolder, final CardPOJO model, final int position) {
//your populate logic
//your existing code here
if (model.isPaymentDone){
//set payment success image holder.card_image.setImageResource(image);
}else{
//set payment failure image
}
//setting the card click listener
viewHolder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
//we have the card click listener, we will start the payment processing in activity
callBackInterface.onClick(position,model);
}
});
}
public void setCallBackInterface(CallBackInterface callBackInterface) {
this.callBackInterface = callBackInterface;
}
}
now almost everything is done we need to call this Custom Firebase adapter and pass the required things and it will do its job.
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final DatabaseReference mDatabaseRef = FirebaseDatabase.getInstance().getReference();
/*
if you have any other database child then you can refer to it using
DatabaseReference child = mDatabaseRef.child("yourchilddatabase");
and pass this to the last argument
*/
final FirebaseRecycler firebaseRecycler = new FirebaseRecycler(CardPOJO.class, R.layout.card_product, CardHolder.class, mDatabaseRef);
firebaseRecycler.setCallBackInterface(new CallBackInterface() {
#Override
public void onClick(final int position, final CardPOJO cardPOJO) {
//start processing the payment
bp = new BillingProcessor() new BillingProcessor.IBillingHandler() {
#Override
public void onProductPurchased(#NonNull String productId, #Nullable TransactionDetails details) {
/**
*when you have processed the payment just enable the flag on server database by having a extra boolean flag for this
* and check in onBindViewHolder if this is enabled if so then replace your image
* updating the values on server, you can handle it according to your user case
*/
cardPOJO.setPaymentDone(true);
mDatabaseRef.push().setValue(cardPOJO);
firebaseRecycler.notifyItemChanged(position);
}
#Override
public void onBillingError(int errorCode, #Nullable Throwable error) {
//existing logic
}
#Override
public void onBillingInitialized() {
//existing logic
}
#Override
public void onPurchaseHistoryRestored() {
//existing logic
}
};
}
});
}
this demonstrates the basic logic you can patch it according to your requirement.
Get your item from RecyclerView's adapter and edit it. Then just call Adapter.onItemChanged(int position), this will cause to call onBindViewholder to be called specifically for that position.
I'm setting an OnClickListener to cardviews on my ViewHolder:
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.list_item, parent, false);
view.setBackgroundResource(mBackground);
return new ViewHolder(view);
}
#Override
public void onBindViewHolder(final ViewHolder holder, int position) {
holder.mBoundString = mValues.get(position);
holder.mTextView.setText(mValues.get(position));
holder.mView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
MainActivity mActivity= new MainActivity();
mActivity.onClickPlayButton(v);
}
});
Glide.with(holder.mImageView.getContext())
.load(Stations.getRandomCheeseDrawable())
.fitCenter()
.into(holder.mImageView);
}
This is my onClickPlayButton on MainActivity:
public void onClickPlayButton(View view) {
radioServiceBinder.play();
}
This is the Service code:
public class RadioService extends Service implements OnErrorListener, OnCompletionListener, OnPreparedListener, OnInfoListener {
private MediaPlayer mediaPlayer;
private List<String> radioStreamURL = new ArrayList<>();
private int radioStreamingIndex = 0;
public static final String MODE_CREATED = "CREATED";
public static final String MODE_DESTROYED = "DESTROYED";
public static final String MODE_PREPARED = "PREPARED";
public static final String MODE_STARTED = "STARTED";
public static final String MODE_PLAYING = "PLAYING";
public static final String MODE_PAUSED = "PAUSED";
public static final String MODE_STOPPED = "STOPPED";
public static final String MODE_COMPLETED = "COMPLETED";
public static final String MODE_ERROR = "ERROR";
public static final String MODE_BUFFERING_START = "BUFFERING_START";
public static final String MODE_BUFFERING_END = "BUFFERING_END";
private boolean isPrepared = false;
private final IBinder binder = new RadioBinder();
#Override
public void onCreate() {
// add stations to the list
radioStreamURL.add("http://momori.animenfo.com:8000/");
radioStreamURL.add("http://momori.animenfo.com:8000/");
radioStreamURL.add("http://momori.animenfo.com:8000/");
/* Create MediaPlayer when it starts for first time */
mediaPlayer = new MediaPlayer();
mediaPlayer.setOnCompletionListener(this);
mediaPlayer.setOnErrorListener(this);
mediaPlayer.setOnPreparedListener(this);
mediaPlayer.setOnInfoListener(this);
sendBroadcast(new Intent(MODE_CREATED));
}
#Override
public void onDestroy() {
super.onDestroy();
mediaPlayer.stop();
mediaPlayer.reset();
isPrepared = false;
sendBroadcast(new Intent(MODE_DESTROYED));
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
sendBroadcast(new Intent(MODE_STARTED));
/* Starts playback at first time or resumes if it is restarted */
if(mediaPlayer.isPlaying())
sendBroadcast(new Intent(MODE_PLAYING));
else if(isPrepared) {
sendBroadcast(new Intent(MODE_PAUSED));
}
return Service.START_STICKY;
}
#Override
public void onPrepared(MediaPlayer _mediaPlayer) {
/* If radio is prepared then start playback */
sendBroadcast(new Intent(MODE_PREPARED));
isPrepared = true;
play();
}
#Override
public void onCompletion(MediaPlayer mediaPlayer) {
/* When no stream found then complete the playback */
mediaPlayer.stop();
mediaPlayer.reset();
isPrepared = false;
sendBroadcast(new Intent(MODE_COMPLETED));
}
public void prepare() {
/* Prepare Async Task - starts buffering */
try {
mediaPlayer.setDataSource(radioStreamURL.get(radioStreamingIndex));
mediaPlayer.prepareAsync();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalStateException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
public void nextStation(){
changeStation(radioStreamingIndex+1);
}
public void prevStation(){
changeStation(radioStreamingIndex-1);
}
public void changeStation(int stationIndex){
if(stationIndex > 0 && stationIndex < radioStreamURL.size()){
radioStreamingIndex = stationIndex;
stop();
play();
}
}
public void play() {
if(isPrepared) {
mediaPlayer.start();
System.out.println("RadioService: play");
//sendBroadcast(new Intent(MODE_PLAYING));
}
else
{
//sendBroadcast(new Intent(MODE_STARTED));
prepare();
}
}
public void pause() {
mediaPlayer.pause();
System.out.println("RadioService: pause");
sendBroadcast(new Intent(MODE_PAUSED));
}
public void stop() {
mediaPlayer.stop();
mediaPlayer.reset();
isPrepared = false;
System.out.println("RadioService: stop");
sendBroadcast(new Intent(MODE_STOPPED));
}
#Override
public boolean onInfo(MediaPlayer mp, int what, int extra) {
/* Check when buffering is started or ended */
if(what == MediaPlayer.MEDIA_INFO_BUFFERING_START) {
sendBroadcast(new Intent(MODE_BUFFERING_START));
}
else if(what == MediaPlayer.MEDIA_INFO_BUFFERING_END) {
sendBroadcast(new Intent(MODE_BUFFERING_END));
}
return false;
}
#Override
public boolean onError(MediaPlayer mp, int what, int extra) {
sendBroadcast(new Intent(MODE_ERROR));
switch (what) {
case MediaPlayer.MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK:
Log.v("ERROR","MEDIA ERROR NOT VALID FOR PROGRESSIVE PLAYBACK " + extra);
break;
case MediaPlayer.MEDIA_ERROR_SERVER_DIED:
Log.v("ERROR","MEDIA ERROR SERVER DIED " + extra);
break;
case MediaPlayer.MEDIA_ERROR_UNKNOWN:
Log.v("ERROR","MEDIA ERROR UNKNOWN " + extra);
break;
}
return false;
}
#Override
public IBinder onBind(Intent intent) {
return binder;
}
/* Allowing activity to access all methods of RadioService */
public class RadioBinder extends Binder {
RadioService getService() {
// Return this instance of RadioService so clients can call public methods
return RadioService.this;
}
}
}
When I click it, I got this error on logcat: java.lang.NullPointerException: Attempt to invoke virtual method ....RadioService.play()' on a null object reference.
I don't think there is anything wrong in the play() method.
I want the OnClickListener or the onClickPlayButton to call the play() method from my Service class that is binded to my MainActivity. How can I achieve this?
I'm stuck at this problem.
Yours error here
MainActivity mActivity= new MainActivity();
mActivity.onClickPlayButton(v);
You try to call method of new instance of Activitys class. But you should call method of current, running instance of yours activity. So you must send reference to yours activity to adapter and call yours method on it.
As I understand, you initialize variables and bind yours Service in OnCreate or onResume of yours activity. But this methods not invoked adter you create a new instance of MainActivity class;
UPD_0:
So yours adapters class should be like this:
public class YOURS_ADAPTERS_CLASS_NAME extends RecyclerView.Adapter<YOURS_ADAPTERS_CLASS_NAME.ViewHolder>
{
Context ctx;
public YOURS_ADAPTERS_CLASS_NAME(Context ctx)
{
this.ctx=ctx;
}
///other lines of yours code
So now you have instance of yours running activity inside yours adapters class. You now should cast it to yours activitys class and call yours method this way:
MainActivity mActivity= (MainActivity) ctx;
mActivity.onClickPlayButton(v);
Also, you don't need (as i can see) an argument in method onClickPlayButton()
I made a simple class that handles everything related to sound. Has an add, play, stop, release and releaseAll. How it works is that you have to add a song and then call play passing the name of the song you added. Anytime you need to stop, just call the stop function and pass the song's name as parameter and it should stop. My issue is that it isn't stopping even though it goes through stop().
Sound class:
public class Sound
{
private Map<String, MediaPlayer> songs = new HashMap<String, MediaPlayer>();
private MediaPlayer currentlyPlayingSong;
public Sound() {}
public void Add(int songId, String songName, Context context)
{
MediaPlayer song = MediaPlayer.create(context, songId);
songs.put(songName, song);
}
public void Play(String name, boolean shouldLoop)
{
MediaPlayer songToPlay = songs.get(name);
if ( songToPlay != currentlyPlayingSong && songToPlay != null)
{
currentlyPlayingSong = songToPlay;
currentlyPlayingSong.start();
currentlyPlayingSong.setLooping(shouldLoop);
}
}
public void Stop(String name)
{
MediaPlayer songToStop = songs.get(name);
if (songToStop != null)
{
songToStop.setLooping(false);
songToStop.stop();
}
}
public void Release(String name)
{
songs.get(name).release();
}
public void ReleaseAll()
{
LinkedList<MediaPlayer> _songs;
_songs = (LinkedList)songs.values();
for (int i = 0; i < _songs.size(); i++)
{
_songs.get(i).release();
}
}
}
On the activity's OnCreate I call Add then Play. Everything is fine until I try to call Stop from a fragment. Runs without any errors or exceptions, it simply doesn't stop.
Activity:
public class Main extends ActionBarActivity
{
private Sound sound = new Sound();
private static boolean isSoundOn = true;
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
isSoundOn = true;
sound.Add(R.raw.drajamainmenueddited, "mainMenuSong", this);
//endregion
//Hide upper action bar
getSupportActionBar().hide();
if (isSoundOn)
sound.Play("mainMenuSong", true);
}
public void SetIsSoundOn(Boolean isOn)
{
isSoundOn = isOn;
}
public boolean GetIsSoundOn()
{
return isSoundOn;
}
public Sound GetSoundObj()
{
return sound;
}
}
Fragment:
public class MainMenuFragment extends Fragment {
private ImageButton soundImgBtn;
private FragmentConfig fragmentConfig;
public MainMenuFragment()
{
fragmentConfig = new FragmentConfig();
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState)
{
//region Initiators
View view = inflater.inflate(R.layout.fragment_main_menu, container, false);
soundImgBtn = (ImageButton)view.findViewById(R.id.soundImgBtn);
//endregion
//region Listeners
soundImgBtn.setOnClickListener(
new View.OnClickListener()
{
#Override
public void onClick(View v)
{
SoundImgBtnClick(v);
}
}
);
//endregion
//Changes audio img
if (((Main)getActivity()).GetIsSoundOn())
soundImgBtn.setImageResource(android.R.drawable.ic_lock_silent_mode_off);
else
soundImgBtn.setImageResource(android.R.drawable.ic_lock_silent_mode);
// Inflate the layout for this fragment
return view;
}
private void SoundImgBtnClick(View v)
{
//if sound is on and clicked, turn off
if (((Main)getActivity()).GetIsSoundOn())
{
((Main)getActivity()).SetIsSoundOn(false);
((Main)getActivity()).GetSoundObj().Stop("mainMenuSong");
soundImgBtn.setImageResource(android.R.drawable.ic_lock_silent_mode);
}
else
{
((Main)getActivity()).SetIsSoundOn(true);
((Main)getActivity()).GetSoundObj().Play("mainMenuSong", true);
soundImgBtn.setImageResource(android.R.drawable.ic_lock_silent_mode_off);
}
}
}
What I'm trying to do is emulate a mute button. Once clicked all sounds should be muted.
This is pretty much all I've coded, so far.
Cheers.
I suspect you're using different instances of MediaPlayer. You are allowed to do that BUT you must stop the song within the same instance.
About the code in Add():
MediaPlayer song = MediaPlayer.create(context, songId);
In Stop():
MediaPlayer songToStop = songs.get(name)
Note:
The above codes tell me you're using different instances of the MediaPlayer for one same song. The object song needs to be declared on a higher scope for you to access it and to stop the song.
Need to call release() method after stop() to free up resources.
try songToStop.release() instead
Got it to stop. My class had to be able to handle one song at a time and many fx at the same time. This is what I came up with.
Sound:
public class Sound
{
private static MediaPlayer currentlyPlayingSong,
currentlyPlayingFX;
public Sound() {}
public void PlayFX(int fxId, Context context, boolean shouldLoop)
{
MediaPlayer fx = MediaPlayer.create(context, fxId);
if (currentlyPlayingFX != fx)
{
StopFX();
currentlyPlayingFX = fx;
currentlyPlayingFX.start();
currentlyPlayingFX.setLooping(shouldLoop);
}
}
public void PlaySong(int songId, boolean shouldLoop, Context context)
{
MediaPlayer song = MediaPlayer.create(context, songId);
if (currentlyPlayingSong != song)
{
StopSong();
currentlyPlayingSong = song;
currentlyPlayingSong.start();
currentlyPlayingSong.setLooping(shouldLoop);
}
}
public void StopFX()
{
if (currentlyPlayingFX != null)
{
currentlyPlayingFX.stop();
currentlyPlayingFX.release();
currentlyPlayingFX = null;
}
}
public void StopSong()
{
if (currentlyPlayingSong != null)
{
currentlyPlayingSong.stop();
currentlyPlayingSong.release();
currentlyPlayingSong = null;
}
}
}
This is was based of what #The Original Android answered. Keep it on a single instance.
Thanks for the help.