Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion bundles/org.openhab.binding.chromecast/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ With manual thing configuration the parameter `port` for audio group must be det
|-------------|---------|----------|----------|---------|------------------------------------------------------|
| ipAddress | - | Yes | No | String | The hostname or IP address of the Chromecast device. |
| port | 8009 | No | Yes | Integer | The port where the Chromecast is listening |
| refreshRate | 10 | No | Yes | Integer | The refresh (poll) interval in seconds. |

## Channels

Expand Down
24 changes: 19 additions & 5 deletions bundles/org.openhab.binding.chromecast/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@

<dependencies>
<dependency>
<groupId>de.sfuhrm</groupId>
<artifactId>chromecast-java-api-v2</artifactId>
<version>0.12.3</version>
<groupId>org.digitalmediaserver</groupId>
<artifactId>cast-api</artifactId>
<version>0.2.0-SNAPSHOT</version>
<scope>compile</scope>
<exclusions>
<exclusion>
Expand All @@ -33,8 +33,8 @@
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-javalite</artifactId>
<version>3.25.5</version>
<artifactId>protobuf-java</artifactId>
<version>3.21.7</version>
<scope>compile</scope>
</dependency>
<dependency>
Expand All @@ -57,4 +57,18 @@
</dependency>
</dependencies>

<repositories>
<repository>
<releases>
<enabled>false</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
<id>central-portal-snapshots</id>
<name>Central Portal Snapshots</name>
<url>https://central.sonatype.com/repository/maven-snapshots/</url>
</repository>
</repositories>

</project>
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public class ChromecastBindingConstants {
Stream.of(THING_TYPE_AUDIO, THING_TYPE_AUDIOGROUP, THING_TYPE_CHROMECAST).collect(Collectors.toSet()));

// Config Parameters
public static final String HOST = "ipAddress";
public static final String HOST = "host";
public static final String PORT = "port";
public static final String DEVICE_ID = "deviceId";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,15 @@

import java.io.IOException;

import org.digitalmediaserver.cast.CastDevice;
import org.digitalmediaserver.cast.Session;
import org.digitalmediaserver.cast.message.entity.Application;
import org.digitalmediaserver.cast.message.entity.Media.MediaBuilder;
import org.digitalmediaserver.cast.message.entity.MediaStatus;
import org.digitalmediaserver.cast.message.entity.ReceiverStatus;
import org.digitalmediaserver.cast.message.enumeration.IdleReason;
import org.digitalmediaserver.cast.message.enumeration.PlayerState;
import org.digitalmediaserver.cast.message.enumeration.StreamType;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.library.types.IncreaseDecreaseType;
Expand All @@ -32,11 +41,6 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import su.litvak.chromecast.api.v2.Application;
import su.litvak.chromecast.api.v2.ChromeCast;
import su.litvak.chromecast.api.v2.MediaStatus;
import su.litvak.chromecast.api.v2.Status;

/**
* This sends the various commands to the Chromecast.
*
Expand All @@ -46,13 +50,15 @@
public class ChromecastCommander {
private final Logger logger = LoggerFactory.getLogger(ChromecastCommander.class);

private final ChromeCast chromeCast;
private final CastDevice chromeCast;
private final ChromecastScheduler scheduler;
private final ChromecastStatusUpdater statusUpdater;

private static final int VOLUMESTEP = 10;

public ChromecastCommander(ChromeCast chromeCast, ChromecastScheduler scheduler,
private static final String SOURCE = "openHAB";

public ChromecastCommander(CastDevice chromeCast, ChromecastScheduler scheduler,
ChromecastStatusUpdater statusUpdater) {
this.chromeCast = chromeCast;
this.scheduler = scheduler;
Expand All @@ -66,6 +72,11 @@ public void handleCommand(final ChannelUID channelUID, final Command command) {
}

switch (channelUID.getId()) {
case CHANNEL_APP_ID:
if (command instanceof StringType) {
startApp(command.toString());
}
break;
case CHANNEL_CONTROL:
handleControl(command);
break;
Expand Down Expand Up @@ -94,9 +105,9 @@ public void handleRefresh() {
return;
}

Status status;
ReceiverStatus status;
try {
status = chromeCast.getStatus();
status = chromeCast.getReceiverStatus();
statusUpdater.processStatusUpdate(status);

if (status == null) {
Expand All @@ -110,13 +121,19 @@ public void handleRefresh() {
}

try {
if (status != null && status.getRunningApp() != null) {
MediaStatus mediaStatus = chromeCast.getMediaStatus();
if (status != null && status.getRunningApplication() instanceof Application application) {
if (application.getTransportId() == null) {
logger.debug("Running app has no transportId, cannot request media status");
return;
}

Session session = chromeCast.startSession(SOURCE, application);
MediaStatus mediaStatus = session.getMediaStatus();
statusUpdater.updateMediaStatus(mediaStatus);

if (mediaStatus != null && mediaStatus.playerState == MediaStatus.PlayerState.IDLE
&& mediaStatus.idleReason != null
&& mediaStatus.idleReason != MediaStatus.IdleReason.INTERRUPTED) {
if (mediaStatus != null && mediaStatus.getPlayerState() == PlayerState.IDLE
&& mediaStatus.getIdleReason() != null
&& mediaStatus.getIdleReason() != IdleReason.INTERRUPTED) {
closeApp(MEDIA_PLAYER);
}
}
Expand All @@ -130,15 +147,15 @@ public void handleCloseApp(final Command command) {
if (command == OnOffType.ON) {
Application app;
try {
app = chromeCast.getRunningApp();
app = chromeCast.getRunningApplication();
} catch (final IOException e) {
logger.info("{} command failed: {}", command, e.getMessage());
statusUpdater.updateStatus(ThingStatus.OFFLINE, COMMUNICATION_ERROR, e.getMessage());
return;
}

if (app != null) {
closeApp(app.id);
closeApp(app.getAppId());
}
}
}
Expand All @@ -151,27 +168,33 @@ private void handlePlayUri(Command command) {

private void handleControl(final Command command) {
try {
Application app = chromeCast.getRunningApp();
Application app = chromeCast.getRunningApplication();
statusUpdater.updateStatus(ThingStatus.ONLINE);
if (app == null) {
logger.debug("{} command ignored because media player app is not running", command);
return;
}

Session session = chromeCast.startSession(SOURCE, app);
MediaStatus mediaStatus = session.getMediaStatus();
logger.debug("mediaStatus {}", mediaStatus);
int mediaSessionId = -1;
if (mediaStatus != null) {
mediaSessionId = mediaStatus.getMediaSessionId();
}

if (command instanceof PlayPauseType playPauseCommand) {
MediaStatus mediaStatus = chromeCast.getMediaStatus();
logger.debug("mediaStatus {}", mediaStatus);
if (mediaStatus == null || mediaStatus.playerState == MediaStatus.PlayerState.IDLE) {
if (mediaStatus == null || mediaStatus.getPlayerState() == PlayerState.IDLE) {
logger.debug("{} command ignored because media is not loaded", command);
return;
}
if (playPauseCommand == PlayPauseType.PLAY) {
chromeCast.play();
session.play(mediaSessionId, false);
} else if (playPauseCommand == PlayPauseType.PAUSE
&& ((mediaStatus.supportedMediaCommands & 0x00000001) == 0x1)) {
chromeCast.pause();
&& ((mediaStatus.getSupportedMediaCommands() & 0x00000001) == 0x1)) {
session.pause(mediaSessionId, false);
} else {
logger.info("{} command not supported by current media", command);
logger.warn("{} command not supported by current media", command);
}
}

Expand All @@ -180,7 +203,7 @@ private void handleControl(final Command command) {
if (command == NextPreviousType.NEXT) {
Double duration = statusUpdater.getLastDuration();
if (duration != null) {
chromeCast.seek(duration.doubleValue() - 5);
session.seek(mediaSessionId, (duration.doubleValue() - 5), null, false);
} else {
logger.info("{} command failed - unknown media duration", command);
}
Expand Down Expand Up @@ -210,7 +233,7 @@ public void handleVolume(final Command command) {

private void setVolumeInternal(PercentType volume) {
try {
chromeCast.setVolumeByIncrement(volume.floatValue() / 100);
chromeCast.setVolumeLevel(volume.floatValue() / 100);
statusUpdater.updateStatus(ThingStatus.ONLINE);
} catch (final IOException ex) {
logger.debug("Set volume failed: {}", ex.getMessage());
Expand All @@ -222,7 +245,7 @@ private void handleMute(final Command command) {
if (command instanceof OnOffType) {
final boolean mute = command == OnOffType.ON;
try {
chromeCast.setMuted(mute);
chromeCast.setMuteState(mute);
statusUpdater.updateStatus(ThingStatus.ONLINE);
} catch (final IOException ex) {
logger.debug("Mute/unmute volume failed: {}", ex.getMessage());
Expand All @@ -236,10 +259,10 @@ public void startApp(@Nullable String appId) {
return;
}
try {
if (chromeCast.isAppAvailable(appId)) {
if (!chromeCast.isAppRunning(appId)) {
final Application app = chromeCast.launchApp(appId);
statusUpdater.setAppSessionId(app.sessionId);
if (chromeCast.isApplicationAvailable(appId)) {
if (!chromeCast.isApplicationRunning(appId)) {
final ReceiverStatus receiverStatus = chromeCast.launchApplication(appId, true);
statusUpdater.setAppSessionId(receiverStatus.getRunningApplication().getSessionId());
logger.debug("Application launched: {}", appId);
}
} else {
Expand All @@ -257,11 +280,11 @@ public void closeApp(@Nullable String appId) {
}

try {
if (chromeCast.isAppRunning(appId)) {
Application app = chromeCast.getRunningApp();
if (app.id.equals(appId)) {
chromeCast.stopApp();
logger.debug("Application closed: {}", appId);
if (chromeCast.isApplicationAvailable(appId)) {
Application app = chromeCast.getRunningApplication();
if (app.getAppId().equals(MEDIA_PLAYER) && app.getSessionId().equals(statusUpdater.getAppSessionId())) {
chromeCast.stopApplication(app, false);
logger.debug("Media player app stopped");
}
}
} catch (final IOException e) {
Expand All @@ -272,15 +295,18 @@ public void closeApp(@Nullable String appId) {
public void playMedia(@Nullable String title, @Nullable String url, @Nullable String mimeType) {
startApp(MEDIA_PLAYER);
try {
if (url != null && chromeCast.isAppRunning(MEDIA_PLAYER)) {
if (url != null && chromeCast.isApplicationRunning(MEDIA_PLAYER)) {
// If the current track is paused, launching a new request results in nothing happening, therefore
// resume current track.
MediaStatus ms = chromeCast.getMediaStatus();
if (ms != null && MediaStatus.PlayerState.PAUSED == ms.playerState && url.equals(ms.media.url)) {
Session session = chromeCast.startSession(SOURCE, chromeCast.getRunningApplication());
MediaStatus ms = session.getMediaStatus();
if (ms != null && PlayerState.PAUSED == ms.getPlayerState()
&& url.equals(ms.getMedia().getUrl())) {
logger.debug("Current stream paused, resuming");
chromeCast.play();
session.play(ms.getMediaSessionId(), false);
} else {
chromeCast.load(title, null, url, mimeType);
MediaBuilder builder = new MediaBuilder(url, mimeType, StreamType.NONE);
session.load(builder, true, 0.0, false);
}
} else {
logger.warn("Missing media player app - cannot process media.");
Expand All @@ -296,4 +322,15 @@ public void playMedia(@Nullable String title, @Nullable String url, @Nullable St
}
}
}

public void dispose() {
scheduler.destroy();
if (chromeCast.isConnected()) {
try {
chromeCast.disconnect();
} catch (IOException e) {
logger.debug("Failed to disconnect from chromecast: {}", e.getMessage());
}
}
}
}
Loading
Loading