将图像(照片)投射到 Chromecast

Cast image (photo) to Chromecast

我正在按照这些 (1, 2) 指南为 Chromecast 创建发件人 Android 应用程序,我只对发送图片感兴趣。 有很多信息和 samples 如何投射 TextAudioVideo .但是没有一个字怎么用 Pictures.

我相信 stackoferflow 的强大功能,应该有人遇到过这样的问题。请提供一些好的示例或教程。我所需要的只是使用 Media Router 及其功能投射全屏图片的指南。

这就是我使用自定义频道发送短信的方式:

 /**
 * Send a text message to the receiver
 */
private void sendMessage(String message) {
    if (mApiClient != null && mSmartBusChannel != null) {
        try {
            Cast.CastApi.sendMessage(mApiClient,
                    mSmartBusChannel.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(this, message, Toast.LENGTH_SHORT)
                .show();
    }
}

正在使用 RemotePlaybackClient 发送视频。好的,图片呢?

非常感谢您的帮助。


编辑:

我找到了可以从本地存储发送图片的方法 (on this blog)。是的,这似乎并没有真正起作用。

public final void openPhotoOnChromecast(String title, String url, String ownerName, String description) {
    try {
        Log.d(TAG, "openPhotoOnChromecast: " + url);
        JSONObject payload = new JSONObject();
        payload.put(KEY_COMMAND, "viewphoto");
        payload.put("fullsizeUrl", url);
        payload.put("ownerName", ownerName);
        payload.put("title", title);
        payload.put("description", description);

        sendMessage(payload);
    } catch (JSONException e) {
        Log.e(TAG, "Cannot parse or serialize data for openPhotoOnChromecast", e);
    } catch (IOException e) {
        Log.e(TAG, "Unable to send openPhotoOnChromecast message", e);
    } catch (IllegalStateException e) {
        Log.e(TAG, "Message Stream is not attached", e);
    }
}

P.S。此方法使用这些库中的 sendMessage(...)(来自 gradle):

compile files('libs/commons-io-2.4.jar')
compile files('libs/GoogleCastSdkAndroid.jar')

看这里:Examples using CastCompanionLibrary to simply display an image 将图像发送到 Chromecast 实际上有三个选项。

  1. 您可以将图像编码为 base64 字符串,然后通过 数据通道到接收器。如果太大,可以拆分 并通过多条消息发送。这是一个非常糟糕的用途 演员技术,真的你不应该这样做,但它是 可能。
  2. 您只需向 Chromecast 设备发送 url 并获取它 从接收器应用程序中的服务器。这个最推荐 将照片发送到 Chromecast 的方法
  3. 如果您不是从服务器下载图像,您可以设置 在您的客户端 Android 应用程序中建立您自己的服务器 运行 并发送一个 url 给接收器从那里抓取它。这倒是 发送图像很复杂,但是更健壮 选项比选项 1.

根据 Google 的说法,Chromecast 的目标是从云端流式传输内容,这就是为什么对发送本地图像没有真正的原生支持。应鼓励开发人员从服务器向接收器应用程序加载图像。

这里有一个很好的文档示例,说明如何从 Linux / Ubuntu 中的本地文件夹制作幻灯片/提供图像: https://github.com/sbow/pyCast

目录/文件类型在运行时指定 - 或者可以使用默认值。

该代码使用模块 pychromecast 并生成一个简单的网络服务器,使图像可用于 Chromecast。

代码示例

创建本地网络服务器

# Start webserver for current directory

def startServer(args, PORT=8000):
    os.chdir(args.directory)
    handler = http.server.SimpleHTTPRequestHandler
    with socketserver.TCPServer(("", PORT), handler) as httpd:
        print("Server started at localhost:" + str(PORT))
        httpd.serve_forever()

# Start new thread for webserver
daemon = threading.Thread(name='daemon_server',
                          target=startServer,
                          args=(args, PORT))
daemon.setDaemon(True) # Set as a daemon so it will be killed once the main thread is dead.
daemon.start()

为本地图像构建URL

# Build uri of first image for slideshow. This is sent to the chromecast. This
# ends up being a mash up of the host ip address, the webserver port, and the
# file name of the image to be displayed.
fileName = os.path.basename(filesAndPath[nStartFile])
fileUrl = urllib.parse.quote(fileName)
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect(("8.8.8.8", 80))
ipAddr = s.getsockname()[0]
fileUri = 'http://'+ipAddr+':'+'8000/'+fileUrl

设置 Chromecast

# -- Setup chromecast --
# List chromecasts on the network, but don't connect
services, browser = pychromecast.discovery.discover_chromecasts()
# Shut down discovery
pychromecast.discovery.stop_discovery(browser)
chromecasts, browser = pychromecast.get_listed_chromecasts(
    friendly_names=[args.cast]
)

if not chromecasts:
    print(f'No chromecast with name "{args.cast}" discovered')
    sys.exit(1)

cast = chromecasts[0]
# Start socket client's worker thread and wait for initial status update
cast.wait()
print(f'Found chromecast with name "{args.cast}", attempting to play "{args.url}"')
cast.media_controller.play_media(fileUri, MEDIA_TAG)

# Wait for player_state PLAYING
player_state = None
has_played = False
# -- end Setup chromecast --

无限循环提供文件夹中的图像

# Enter the infinite loop where successive images are displayed via the
# chromecast, by sending it image uri's served by our scripts webserver,
# linking the chromecast to images in our directory.
iPhoto = nStartFile 
iPhotoMax = nFiles-1 
while True:
    try:
        if player_state != cast.media_controller.status.player_state:
            player_state = cast.media_controller.status.player_state
            print("Player state:", player_state)
        if player_state == "PLAYING":
            has_played = True
        if cast.socket_client.is_connected and has_played and player_state != "PLAYING":
            has_played = False
            cast.media_controller.play_media(args.url, "audio/mp3")

        time.sleep(args.pause)
        if args.do_random:
            nRandom = random.random()*nFiles
            iPhoto = round(nRandom)
        else:
            iPhoto = iPhoto + 1
            if iPhoto > iPhotoMax:
                iPhoto = 0
        fileName = os.path.basename(filesAndPath[iPhoto])
        fileUrl = urllib.parse.quote(fileName)
        fileUri = 'http://'+ipAddr+':'+'8000/'+fileUrl
        cast.media_controller.play_media(fileUri, MEDIA_TAG)

    except KeyboardInterrupt:
        break

完整节目pyCast.py

"""
Play a slideshow on the chromecast
This program allows the user to cast images to their chromecast.
The images are of a particular type ie: ".JPEG" or ".jpg" or ".png",
and contained in a single folder. These parameters are provided,
among others, at command line invocation - or through tuning of
the default parameters below.
Arguments
__________
--show-debug : (none)
    Show debugging information. False if not provided.
--do-random : (none)
    Select image order at random. Ls order if not provided.
--media-flag : '*.jpeg'
    Indicate via a command line regex file type to show
--media-tag : 'image/jpeg'
    Indicate http object type
--cast : 'MyKitchenChromecast'
    Provide friendly name of chromecast
--directory : '/home/barack/SecretPix'
    Provide absolute path to directory for slideshow
--pause : 69
    Number of seconds to hold each image in slideshow
Returns
_______
does not return. Ctrl-C to exit, or launch with "&" and kill process
Examples
______
python pyCast.py --show-debug --media-flag '*.JPEG' --media-tag 'image/jpeg'
--cast 'MyChromecast' --directory '/home/dorthy/OzGirlSummerPics' --do-random
"""
# pylint: disable=invalid-name

import argparse
import logging
import sys
import time
import pychromecast
import pprint
import glob
import os
import urllib.parse
import socket
import http.server
import socketserver
import threading
import random

# Authorship information
__author__ = "Shaun Bowman"
__copyright__ = "Copywrong 2022, Mazeltough Project"
__credits__ = ["SoManyCopyPastes... sorry i dont know the names", "Mom"]
__license__ = "MIT"
__version__ = "0.420.69"
__maintainer__ = "Shaun Bowman"
__email__ = "dm@me.com"
__status__ = "AlphaAF"

# Change to the friendly name of your Chromecast
CAST_NAME = 'ShaunsOfficeMonitor'

# Set webserver port
PORT = 8000

# Set time for photo
PAUSE = 120

# Set media type
MEDIA_FLAG = "*.JPEG"
MEDIA_TAG = "image/jpeg"

# Change to an audio or video url
MEDIA_URL ="http://192.168.0.222:8000/Screenshot%20from%202021-01-24%2023-11-40.png"
MEDIA_DIR = "./"
parser = argparse.ArgumentParser(
    description="Play a slideshow on Chromecast using all images of a given "+
    "type in a given directory."
)
parser.add_argument("--show-debug", help="Enable debug log", action="store_true")
parser.add_argument("--do-random", help="Pick media in dir at random, default false",
                    action="store_false")
parser.add_argument(
    "--media-flag", help="Media flag like *.JPEG or *.png", default=MEDIA_FLAG
)
parser.add_argument(
    "--media-tag", help="Media tag like 'image/jpeg' or 'image/png'",
    default=MEDIA_TAG
)
parser.add_argument(
    "--pause", help="Number of seconds per photograph during slideshow",
    default=PAUSE
)
parser.add_argument(
    "--cast", help='Name of cast device (default: "%(default)s")', default=CAST_NAME
)
parser.add_argument(
    "--url", help='Media url (default: "%(default)s")', default=MEDIA_URL
)
parser.add_argument(
    "--directory", help='Directory containing media to cast', default=MEDIA_DIR
)
args = parser.parse_args()

if args.show_debug:
    logging.basicConfig(level=logging.DEBUG)

# Start webserver for current directory
def startServer(args, PORT=8000):
    os.chdir(args.directory)
    handler = http.server.SimpleHTTPRequestHandler
    with socketserver.TCPServer(("", PORT), handler) as httpd:
        print("Server started at localhost:" + str(PORT))
        httpd.serve_forever()

# Start new thread for webserver
daemon = threading.Thread(name='daemon_server',
                          target=startServer,
                          args=(args, PORT))
daemon.setDaemon(True) # Set as a daemon so it will be killed once the main thread is dead.
daemon.start()

# Wait for stuff... maybe useless
time.sleep(2)

# Get list of files of specific type, in specific directory
pprint.pprint(glob.glob(args.directory+"/"+MEDIA_FLAG))
filesAndPath = glob.glob(args.directory+"/"+MEDIA_FLAG)
nFiles = len(filesAndPath)
if (nFiles==0):
    pprint.pprint("Error: No files found")
    sys.exit(1)

# Select starting point for slideshow
random.seed()
nRandom = random.random()*nFiles
nStartFile = round(nRandom)

# Build uri of first image for slideshow. This is sent to the chromecast. This
# ends up being a mash up of the host ip address, the webserver port, and the
# file name of the image to be displayed.
fileName = os.path.basename(filesAndPath[nStartFile])
fileUrl = urllib.parse.quote(fileName)
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect(("8.8.8.8", 80))
ipAddr = s.getsockname()[0]
fileUri = 'http://'+ipAddr+':'+'8000/'+fileUrl

# -- Setup chromecast --
# List chromecasts on the network, but don't connect
services, browser = pychromecast.discovery.discover_chromecasts()
# Shut down discovery
pychromecast.discovery.stop_discovery(browser)
chromecasts, browser = pychromecast.get_listed_chromecasts(
    friendly_names=[args.cast]
)
if not chromecasts:
    print(f'No chromecast with name "{args.cast}" discovered')
    sys.exit(1)

cast = chromecasts[0]
# Start socket client's worker thread and wait for initial status update
cast.wait()
print(f'Found chromecast with name "{args.cast}", attempting to play "{args.url}"')
cast.media_controller.play_media(fileUri, MEDIA_TAG)

# Wait for player_state PLAYING
player_state = None
has_played = False
# -- end Setup chromecast --

# Enter the infinite loop where successive images are displayed via the
# chromecast, by sending it image uri's served by our scripts webserver,
# linking the chromecast to images in our directory.
iPhoto = nStartFile 
iPhotoMax = nFiles-1 
while True:
    try:
        if player_state != cast.media_controller.status.player_state:
            player_state = cast.media_controller.status.player_state
            print("Player state:", player_state)
        if player_state == "PLAYING":
            has_played = True
        if cast.socket_client.is_connected and has_played and player_state != "PLAYING":
            has_played = False
            cast.media_controller.play_media(args.url, "audio/mp3")

        time.sleep(args.pause)
        if args.do_random:
            nRandom = random.random()*nFiles
            iPhoto = round(nRandom)
        else:
            iPhoto = iPhoto + 1
            if iPhoto > iPhotoMax:
                iPhoto = 0
        fileName = os.path.basename(filesAndPath[iPhoto])
        fileUrl = urllib.parse.quote(fileName)
        fileUri = 'http://'+ipAddr+':'+'8000/'+fileUrl
        cast.media_controller.play_media(fileUri, MEDIA_TAG)

    except KeyboardInterrupt:
        break

# Shut down discovery
browser.stop_discovery()