【问题标题】:Examples using CastCompanionLibrary to simply display an image使用 CastCompanionLibrary 简单显示图像的示例
【发布时间】:2014-04-03 00:28:40
【问题描述】:

我正在寻找在 android 中将图像投射到 chromecast 的示例。奇怪的是,googlecast 示例存储库中似乎没有涵盖这一点。有没有人有一个简单的实现?我基本上想在我的 android 设备上单击我的应用程序照片库中的图像并将其投射到屏幕上。 一个问题是,图片是否需要在网址上?或者是否可以将图像流式传输到设备?提前感谢您的帮助。

【问题讨论】:

  • 我相信它必须在一个 URL 上

标签: android google-cast


【解决方案1】:

我在没有 CastCompanionLibrary 的情况下解决了这个问题,但基于 google 的 CastHelloText-android 示例。基本上我所做的是:

  1. 将图像编码为 base64 字符串并将其作为消息发送到自定义接收器
  2. 修改样本的接收器以接收base64字符串并将其设置为图像源。
  3. upload and register 我的接收者并让应用程序使用生成的应用程序 ID

这是接收方的代码:

<!DOCTYPE html>
<html>
  <head>
    <style>
      img#androidImage {
        height:auto;
        width:100%;
      }
    </style>
    <title>Cast Hello Text</title>
  </head>
  <body>
    <img id="androidImage" src="" />
    <script type="text/javascript" src="//www.gstatic.com/cast/sdk/libs/receiver/2.0.0/cast_receiver.js"></script>
    <script type="text/javascript">
      window.onload = function() {
        cast.receiver.logger.setLevelValue(0);
        window.castReceiverManager = cast.receiver.CastReceiverManager.getInstance();
        console.log('Starting Receiver Manager');

        // handler for the 'ready' event
        castReceiverManager.onReady = function(event) {
          console.log('Received Ready event: ' + JSON.stringify(event.data));
          window.castReceiverManager.setApplicationState("Application status is ready...");
        };

        // handler for 'senderconnected' event
        castReceiverManager.onSenderConnected = function(event) {
          console.log('Received Sender Connected event: ' + event.data);
          console.log(window.castReceiverManager.getSender(event.data).userAgent);
        };

        // handler for 'senderdisconnected' event
        castReceiverManager.onSenderDisconnected = function(event) {
          console.log('Received Sender Disconnected event: ' + event.data);
          if (window.castReceiverManager.getSenders().length == 0) {
            window.close();
          }
        };

        // handler for 'systemvolumechanged' event
        castReceiverManager.onSystemVolumeChanged = function(event) {
          console.log('Received System Volume Changed event: ' + event.data['level'] + ' ' +
              event.data['muted']);
        };

        // create a CastMessageBus to handle messages for a custom namespace
        window.messageBus =
          window.castReceiverManager.getCastMessageBus(
              'urn:x-cast:com.google.cast.sample.helloworld');

        // handler for the CastMessageBus message event
        window.messageBus.onMessage = function(event) {
          console.log('Message recieved');

          var obj = JSON.parse(event.data)

          console.log('Message type: ' + obj.type);
          if (obj.type == "text") {
            console.log('Skipping message: ' + obj.data);
          }

          if (obj.type == "image") {
            var source = 'data:image/png;base64,'.concat(obj.data)
            displayImage(source);
          }

          // inform all senders on the CastMessageBus of the incoming message event
          // sender message listener will be invoked
          window.messageBus.send(event.senderId, event.data);
        }

        // initialize the CastReceiverManager with an application status message
        window.castReceiverManager.start({statusText: "Application is starting"});
        console.log('Receiver Manager started');
      };


      function displayImage(source) {
        console.log('received image');
        document.getElementById("androidImage").src=source;
        window.castReceiverManager.setApplicationState('image source changed');
      };

    </script>
  </body>
</html>

下面是修改后的 MainActivity.java 代码。注册接收器应用程序后,不要忘记修改 string.xml 中的 app_id。

2 个注释:

  • 发送的消息被包裹在一个 JSON 对象中,所以我可以过滤掉 短信。
  • ENCODED_IMAGE_STRING 变量未在此定义 例如,您必须自己查找图像并将其转换为 base64 字符串。

MainActivity.java:

package com.example.casthelloworld;

import java.io.IOException;
import java.util.ArrayList;

import android.content.Intent;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.speech.RecognizerIntent;
import android.support.v4.view.MenuItemCompat;
import android.support.v7.app.ActionBar;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.app.MediaRouteActionProvider;
import android.support.v7.media.MediaRouteSelector;
import android.support.v7.media.MediaRouter;
import android.support.v7.media.MediaRouter.RouteInfo;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.Toast;

import com.google.android.gms.cast.ApplicationMetadata;
import com.google.android.gms.cast.Cast;
import com.google.android.gms.cast.Cast.ApplicationConnectionResult;
import com.google.android.gms.cast.Cast.MessageReceivedCallback;
import com.google.android.gms.cast.CastDevice;
import com.google.android.gms.cast.CastMediaControlIntent;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.ResultCallback;
import com.google.android.gms.common.api.Status;
/**
 * Main activity to send messages to the receiver.
 */
public class MainActivity extends ActionBarActivity {

    private static final String TAG = MainActivity.class.getSimpleName();

    private static final int REQUEST_CODE = 1;

    private MediaRouter mMediaRouter;
    private MediaRouteSelector mMediaRouteSelector;
    private MediaRouter.Callback mMediaRouterCallback;
    private CastDevice mSelectedDevice;
    private GoogleApiClient mApiClient;
    private Cast.Listener mCastListener;
    private ConnectionCallbacks mConnectionCallbacks;
    private ConnectionFailedListener mConnectionFailedListener;
    private HelloWorldChannel mHelloWorldChannel;
    private boolean mApplicationStarted;
    private boolean mWaitingForReconnect;
    private String mSessionId;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ActionBar actionBar = getSupportActionBar();
        actionBar.setBackgroundDrawable(new ColorDrawable(
                android.R.color.transparent));

        // When the user clicks on the button, use Android voice recognition to
        // get text
        Button voiceButton = (Button) findViewById(R.id.voiceButton);
        voiceButton.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                startVoiceRecognitionActivity();
            }
        });


        // When the user clicks on the button, use Android voice recognition to
        // get text
        Button yarrButton = (Button) findViewById(R.id.tmpButton);
        yarrButton.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                castImage();
            }
        });

        // Configure Cast device discovery
        mMediaRouter = MediaRouter.getInstance(getApplicationContext());
        mMediaRouteSelector = new MediaRouteSelector.Builder()
                .addControlCategory(
                        CastMediaControlIntent.categoryForCast(getResources()
                                .getString(R.string.app_id))).build();
        mMediaRouterCallback = new MyMediaRouterCallback();
    }

    private void castImage()
    {
        Log.d(TAG, "castImage()");

        String image_string = createJsonMessage(MessageType.image, ENCODED_IMAGE_STRING);

        sendMessage(image_string);
    }

    /**
     * Android voice recognition
     */
    private void startVoiceRecognitionActivity() {
        Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
        intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,
                RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
        intent.putExtra(RecognizerIntent.EXTRA_PROMPT,
                getString(R.string.message_to_cast));
        startActivityForResult(intent, REQUEST_CODE);
    }

    /*
     * Handle the voice recognition response
     * 
     * @see android.support.v4.app.FragmentActivity#onActivityResult(int, int,
     * android.content.Intent)
     */
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == REQUEST_CODE && resultCode == RESULT_OK) {
            ArrayList<String> matches = data
                    .getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS);
            if (matches.size() > 0) {
                Log.d(TAG, matches.get(0));
                String message = createJsonMessage(MessageType.text, matches.get(0));
                sendMessage(message);
            }
        }
        super.onActivityResult(requestCode, resultCode, data);
    }

    @Override
    protected void onResume() {
        super.onResume();
        // Start media router discovery
        mMediaRouter.addCallback(mMediaRouteSelector, mMediaRouterCallback,
                MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY);
    }

    @Override
    protected void onPause() {
        if (isFinishing()) {
            // End media router discovery
            mMediaRouter.removeCallback(mMediaRouterCallback);
        }
        super.onPause();
    }

    @Override
    public void onDestroy() {
        teardown();
        super.onDestroy();
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        super.onCreateOptionsMenu(menu);
        getMenuInflater().inflate(R.menu.main, menu);
        MenuItem mediaRouteMenuItem = menu.findItem(R.id.media_route_menu_item);
        MediaRouteActionProvider mediaRouteActionProvider = (MediaRouteActionProvider) MenuItemCompat
                .getActionProvider(mediaRouteMenuItem);
        // Set the MediaRouteActionProvider selector for device discovery.
        mediaRouteActionProvider.setRouteSelector(mMediaRouteSelector);
        return true;
    }

    /**
     * Callback for MediaRouter events
     */
    private class MyMediaRouterCallback extends MediaRouter.Callback {

        @Override
        public void onRouteSelected(MediaRouter router, RouteInfo info) {
            Log.d(TAG, "onRouteSelected");
            // Handle the user route selection.
            mSelectedDevice = CastDevice.getFromBundle(info.getExtras());

            launchReceiver();
        }

        @Override
        public void onRouteUnselected(MediaRouter router, RouteInfo info) {
            Log.d(TAG, "onRouteUnselected: info=" + info);
            teardown();
            mSelectedDevice = null;
        }
    }

    /**
     * Start the receiver app
     */
    private void launchReceiver() {
        try {
            mCastListener = new Cast.Listener() {

                @Override
                public void onApplicationDisconnected(int errorCode) {
                    Log.d(TAG, "application has stopped");
                    teardown();
                }

            };
            // Connect to Google Play services
            mConnectionCallbacks = new ConnectionCallbacks();
            mConnectionFailedListener = new ConnectionFailedListener();
            Cast.CastOptions.Builder apiOptionsBuilder = Cast.CastOptions
                    .builder(mSelectedDevice, mCastListener);
            mApiClient = new GoogleApiClient.Builder(this)
                    .addApi(Cast.API, apiOptionsBuilder.build())
                    .addConnectionCallbacks(mConnectionCallbacks)
                    .addOnConnectionFailedListener(mConnectionFailedListener)
                    .build();

            mApiClient.connect();
        } catch (Exception e) {
            Log.e(TAG, "Failed launchReceiver", e);
        }
    }

    /**
     * Google Play services callbacks
     */
    private class ConnectionCallbacks implements
            GoogleApiClient.ConnectionCallbacks {
        @Override
        public void onConnected(Bundle connectionHint) {
            Log.d(TAG, "onConnected");

            if (mApiClient == null) {
                // We got disconnected while this runnable was pending
                // execution.
                return;
            }

            try {
                if (mWaitingForReconnect) {
                    mWaitingForReconnect = false;

                    // Check if the receiver app is still running
                    if ((connectionHint != null)
                            && connectionHint
                                    .getBoolean(Cast.EXTRA_APP_NO_LONGER_RUNNING)) {
                        Log.d(TAG, "App  is no longer running");
                        teardown();
                    } else {
                        // Re-create the custom message channel
                        try {
                            Cast.CastApi.setMessageReceivedCallbacks(
                                    mApiClient,
                                    mHelloWorldChannel.getNamespace(),
                                    mHelloWorldChannel);
                        } catch (IOException e) {
                            Log.e(TAG, "Exception while creating channel", e);
                        }
                    }
                } else {
                    // Launch the receiver app
                    Cast.CastApi
                            .launchApplication(mApiClient,
                                    getString(R.string.app_id), false)
                            .setResultCallback(
                                    new ResultCallback<Cast.ApplicationConnectionResult>() {
                                        @Override
                                        public void onResult(
                                                ApplicationConnectionResult result) {
                                            Status status = result.getStatus();
                                            Log.d(TAG,
                                                    "ApplicationConnectionResultCallback.onResult: statusCode "
                                                            + status.getStatusCode());
                                            if (status.isSuccess()) {
                                                ApplicationMetadata applicationMetadata = result
                                                        .getApplicationMetadata();
                                                mSessionId = result
                                                        .getSessionId();
                                                String applicationStatus = result
                                                        .getApplicationStatus();
                                                boolean wasLaunched = result
                                                        .getWasLaunched();
                                                Log.d(TAG,
                                                        "application name: "
                                                                + applicationMetadata
                                                                        .getName()
                                                                + ", status: "
                                                                + applicationStatus
                                                                + ", sessionId: "
                                                                + mSessionId
                                                                + ", wasLaunched: "
                                                                + wasLaunched);
                                                mApplicationStarted = true;

                                                // Create the custom message
                                                // channel
                                                mHelloWorldChannel = new HelloWorldChannel();
                                                try {
                                                    Cast.CastApi
                                                            .setMessageReceivedCallbacks(
                                                                    mApiClient,
                                                                    mHelloWorldChannel
                                                                            .getNamespace(),
                                                                    mHelloWorldChannel);
                                                } catch (IOException e) {
                                                    Log.e(TAG,
                                                            "Exception while creating channel",
                                                            e);
                                                }

                                                // set the initial instructions
                                                // on the receiver
                                                String message = createJsonMessage(MessageType.text, getString(R.string.instructions));
                                                sendMessage(message);
                                            } else {
                                                Log.e(TAG,
                                                        "application could not launch");
                                                teardown();
                                            }
                                        }
                                    });
                }
            } catch (Exception e) {
                Log.e(TAG, "Failed to launch application", e);
            }
        }

        @Override
        public void onConnectionSuspended(int cause) {
            Log.d(TAG, "onConnectionSuspended");
            mWaitingForReconnect = true;
        }
    }

    /**
     * Google Play services callbacks
     */
    private class ConnectionFailedListener implements
            GoogleApiClient.OnConnectionFailedListener {
        @Override
        public void onConnectionFailed(ConnectionResult result) {
            Log.e(TAG, "onConnectionFailed ");

            teardown();
        }
    }

    /**
     * Tear down the connection to the receiver
     */
    private void teardown() {
        Log.d(TAG, "teardown");
        if (mApiClient != null) {
            if (mApplicationStarted) {
                if (mApiClient.isConnected()  || mApiClient.isConnecting()) {
                    try {
                        Cast.CastApi.stopApplication(mApiClient, mSessionId);
                        if (mHelloWorldChannel != null) {
                            Cast.CastApi.removeMessageReceivedCallbacks(
                                    mApiClient,
                                    mHelloWorldChannel.getNamespace());
                            mHelloWorldChannel = null;
                        }
                    } catch (IOException e) {
                        Log.e(TAG, "Exception while removing channel", e);
                    }
                    mApiClient.disconnect();
                }
                mApplicationStarted = false;
            }
            mApiClient = null;
        }
        mSelectedDevice = null;
        mWaitingForReconnect = false;
        mSessionId = null;
    }

    /**
     * Send a text message to the receiver
     * 
     * @param message
     */
    private void sendMessage(String message) {
        if (mApiClient != null && mHelloWorldChannel != null) {
            try {
                Cast.CastApi.sendMessage(mApiClient,
                        mHelloWorldChannel.getNamespace(), message)
                        .setResultCallback(new ResultCallback<Status>() {
                            @Override
                            public void onResult(Status result) {
                                if (!result.isSuccess()) {
                                    Log.e(TAG, "Sending message failed");
                                }
                            }
                        });
            } catch (Exception e) {
                Log.e(TAG, "Exception while sending message", e);
            }
        } else {
            Toast.makeText(MainActivity.this, message, Toast.LENGTH_SHORT)
                    .show();
        }
    }

    /**
     * Custom message channel
     */
    class HelloWorldChannel implements MessageReceivedCallback {

        /**
         * @return custom namespace
         */
        public String getNamespace() {
            return getString(R.string.namespace);
        }

        /*
         * Receive message from the receiver app
         */
        @Override
        public void onMessageReceived(CastDevice castDevice, String namespace,
                String message) {
            Log.d(TAG, "onMessageReceived: " + message);
        }

    }

    enum MessageType {
        text,
        image,
    }

    public static Bitmap getBitmapFromView(View view) {
        //Define a bitmap with the same size as the view
        Bitmap returnedBitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(),Bitmap.Config.ARGB_8888);
        //Bind a canvas to it
        Canvas canvas = new Canvas(returnedBitmap);
        //Get the view's background
        Drawable bgDrawable =view.getBackground();
        if (bgDrawable!=null)
            //has background drawable, then draw it on the canvas
            bgDrawable.draw(canvas);
        else
            //does not have background drawable, then draw white background on the canvas
            canvas.drawColor(Color.WHITE);
        // draw the view on the canvas
        view.draw(canvas);
        //return the bitmap
        return returnedBitmap;
    }

    private static String createJsonMessage(MessageType type, String message)
    {
        return String.format("{\"type\":\"%s\", \"data\":\"%s\"}", type.toString(), message);
    }
}

【讨论】:

  • 您能否描述一下如何上传自定义接收器的步骤?我刚刚创建了 .js 文件并上传到 Google Drive 并让世界可见。将链接放入 Cast Console 后。并且 Chromecast 连接设备后似乎无法安装此接收器。
【解决方案2】:

由于在 Chromecast 上您的应用程序在网络浏览器中运行,因此您需要有一个 &lt;img/&gt; 标记来显示图像。该标签的src 属性应该指向您想要查看的图像,并且它必须是一个 url,因此如果您的图像驻留在手机的本地存储中,您需要在移动应用程序中启动一个小型 Web 服务器提供该图像并与接收者通信它应该指向的 url(这将是您的服务器提供该图像的 url)。这些都是可行的,如果需要,您可以使用 CastCompanionLibrary 与您的自定义接收器进行通信;只需使用 DataCastManager 类而不是 VideoCastManager

【讨论】:

    【解决方案3】:

    可能我的回答会对其他开发者有所帮助,因为我也没有找到好的解决方案并自己完成了。

    要从您的应用通过 Google Cast 在您的设备屏幕上显示图像,您可以从您的应用创建并启动简单的网络服务器,该服务器将处理带有所选图像名称或 URL 中的 ID 的 http 请求。

    例子:

    public class MyWebServer {
    
        private Activity activity;
        private static ServerSocket httpServerSocket;
        private static boolean isWebServerSunning;
        public static final String drawableDelimiter = "pic-"
    
        public MyWebServer(Activity activity) {
            this.activity = activity;
        }
    
        public void stopWebServer() {
            isWebServerSunning = false;
            try {
                if (httpServerSocket != null) {
                    httpServerSocket.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        public void startWebServer() {
            isWebServerSunning = true;
            Thread webServerThread = new Thread(() -> {
                Socket socket;
                HttpResponseThread httpResponseThread;
                try {
                    httpServerSocket = new ServerSocket(5050);
                    while (isWebServerSunning) {
                        socket = httpServerSocket.accept();
                        httpResponseThread = new HttpResponseThread(socket);
                        httpResponseThread.start();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
            webServerThread.start();
        }
    
        private class HttpResponseThread extends Thread {
    
            Socket clientSocket;
    
            HttpResponseThread(Socket socket) {
                this.clientSocket = socket;
            }
    
            @Override
            public void run() {
                try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
                     OutputStream outputStream = clientSocket.getOutputStream();
                ) {
    
                    String input = bufferedReader.readLine();
                    if (input != null && !input.isEmpty() && input.contains("/") && input.contains(" ")) {
    
                        if (input.contains(drawableDelimiter)) {
    
                            String imageId = input.substring(input.indexOf("/") + 1, input.lastIndexOf(" ")).trim().split(drawableDelimiter)[1];
    
                            Bitmap bitmap = BitmapFactory.decodeResource(activity.getResources(), Integer.parseInt(imageId));
                            if (bitmap != null) {
                                ByteArrayOutputStream bitmapBytes = new ByteArrayOutputStream();
                                bitmap.compress(Bitmap.CompressFormat.PNG, 100, bitmapBytes);
    
                                outputStream.write("HTTP/1.0 200 OK\r\n".getBytes());
                                outputStream.write("Server: Apache/0.8.4\r\n".getBytes());
                                outputStream.write(("Content-Length: " + bitmapBytes.toByteArray().length + "\r\n").getBytes());
                                outputStream.write("\r\n".getBytes());
                                outputStream.write(bitmapBytes.toByteArray());
                            }
                        }
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
    

    只需在 Google Cast 可用或停止时启动或停止您的网络服务器。

    MyWebServer myWebServer = new MyWebServer(this); // pass your activity here
    myWebServer.startWebServer();
    myWebServer.stopWebServer();
    

    【讨论】:

    • 如何将演员会话指向本地网络服务器?
    • 此解决方案仅适用于嵌入式 Web 服务器
    猜你喜欢
    • 2011-08-08
    • 2014-06-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-01-28
    • 1970-01-01
    • 2017-05-28
    • 1970-01-01
    相关资源
    最近更新 更多