package eu.hgross.blaubotcam.views; import android.content.Context; import android.graphics.Bitmap; import android.os.Handler; import android.os.Looper; import android.util.AttributeSet; import android.view.View; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.RelativeLayout; import android.widget.TextView; import java.util.Date; import java.util.concurrent.ConcurrentHashMap; import eu.hgross.blaubot.android.views.KingdomView; import eu.hgross.blaubot.core.State; import eu.hgross.blaubot.admin.AbstractAdminMessage; import eu.hgross.blaubot.admin.CensusMessage; import eu.hgross.blaubot.messaging.BlaubotMessage; import eu.hgross.blaubot.messaging.IBlaubotAdminMessageListener; import eu.hgross.blaubot.messaging.IBlaubotMessageListener; import eu.hgross.blaubotcam.video.ImageMessage; import eu.hgross.blaubotcam.R; /** * Displays ImageMessages. * Attach it to the channel from where you receive the ImageMessages. * * @author Henning Gross {@literal (mail.to@henning-gross.de)} */ public class VideoViewer extends LinearLayout implements IBlaubotMessageListener, IBlaubotAdminMessageListener { private static final String LOG_TAG = "VideoViewer"; private Handler mUiHandler; /** * Maps the unique device id to the view, displaying the last image */ private ConcurrentHashMap<String, View> mViews; /** * The last census message. May be null. */ private CensusMessage mLastCensusMessage; /** * A cache for bitmaps filled when ImageMessages arrive * uniquedeviceid- > bitmap */ private ConcurrentHashMap<String, Bitmap> mBitmapCache; public VideoViewer(Context context, AttributeSet attrs) { super(context, attrs); initView(); } public VideoViewer(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); initView(); } private void initView() { mViews = new ConcurrentHashMap<>(); mBitmapCache = new ConcurrentHashMap<>(); mUiHandler = new Handler(Looper.getMainLooper()); } /** * updates the state images for all clients and the image only for the given uniqueDeviceId * * @param message the image message that needs to be rendered */ private void updateUI(ImageMessage message) { final String uniqueDeviceId = message.getUniqueDeviceId(); final Date time = message.getTime(); // pre-process the bitmap conversion to not do this on the ui thread. mBitmapCache.put(uniqueDeviceId, message.toBitmap()); mUiHandler.post(new Runnable() { @Override public void run() { final String senderUniqueDeviceId = uniqueDeviceId; View item = inflate(getContext(), R.layout.video_viewer_item, null); final boolean added = mViews.putIfAbsent(senderUniqueDeviceId, item) == null; item = mViews.get(senderUniqueDeviceId); FrameLayout nameContainer = (FrameLayout) item.findViewById(R.id.nameContainer); TextView timestampTextView = (TextView) item.findViewById(R.id.timestampTextView); ImageView imageView = (ImageView) item.findViewById(R.id.imageView); // set the timestamp timestampTextView.setText(time.toString()); // set the new picture for the uniqueDeviceId, for which this was requested Bitmap bm = mBitmapCache.get(uniqueDeviceId); if (bm == null) { // something went sideways decoding a bitmap! } else { imageView.setImageBitmap(bm); // set the name container final int width = bm.getWidth(); final int height = bm.getHeight(); nameContainer.removeAllViews(); nameContainer.setLayoutParams(new RelativeLayout.LayoutParams(width, RelativeLayout.LayoutParams.WRAP_CONTENT)); // set the wrapping containers dimensions to the pictures dimensions item.setLayoutParams(new LinearLayout.LayoutParams(width, height)); } // add the sender's data View kingdomViewListItem = null; if (mLastCensusMessage != null) { final State state = mLastCensusMessage.getDeviceStates().get(senderUniqueDeviceId); if (state != null) { kingdomViewListItem = KingdomView.createKingdomViewListItem(getContext(), state, senderUniqueDeviceId); } } // if we don't have the state in the last message, we know the instance is not part of the network // we display the stopped icon assumg it is stopped, dead or ... if (kingdomViewListItem == null) { kingdomViewListItem = KingdomView.createKingdomViewListItem(getContext(), State.Stopped, senderUniqueDeviceId); } // add the namecontainer's content nameContainer.addView(kingdomViewListItem); // only add, if view was newly created (not reused) if (added) { addView(item); } } }); } @Override public void onMessage(BlaubotMessage blaubotMessage) { final ImageMessage imageMessage = new ImageMessage(blaubotMessage.getPayload()); updateUI(imageMessage); } @Override public void onAdminMessage(AbstractAdminMessage adminMessage) { if (adminMessage instanceof CensusMessage) { mLastCensusMessage = (CensusMessage) adminMessage; } } }