├── Images └── UI.jpg ├── GoogleCast ├── Channels │ ├── IHeartbeatChannel.cs │ ├── INextMessageChannel.cs │ ├── IPreviousMessageChannel.cs │ ├── NextMessageChannel.cs │ ├── PreviousMessageChannel.cs │ ├── IApplicationChannel.cs │ ├── IConnectionChannel.cs │ ├── IStatusChannel.cs │ ├── IChannel.cs │ ├── HeartbeatChannel.cs │ ├── ConnectionChannel.cs │ ├── IReceiverChannel.cs │ ├── StatusChannel.cs │ ├── Channel.cs │ ├── ReceiverChannel.cs │ ├── IMediaChannel.cs │ └── MediaChannel.cs ├── Messages │ ├── Hearbeat │ │ ├── PongMessage.cs │ │ └── PingMessage.cs │ ├── Receiver │ │ ├── StopMessage.cs │ │ ├── GetStatusMessage.cs │ │ ├── ReceiverStatusMessage.cs │ │ ├── LaunchMessage.cs │ │ └── SetVolumeMessage.cs │ ├── Connection │ │ ├── ConnectMessage.cs │ │ └── CloseMessage.cs │ ├── Media │ │ ├── QueueMessage.cs │ │ ├── GetStatusMessage.cs │ │ ├── StopMessage.cs │ │ ├── PauseMessage.cs │ │ ├── SkipForwardMessage.cs │ │ ├── SkipBackwardMessage.cs │ │ ├── PlayMessage.cs │ │ ├── NextMessage.cs │ │ ├── GetQueueItemIdsMessage.cs │ │ ├── PreviousMessage.cs │ │ ├── MediaStatusMessage.cs │ │ ├── PositionChangeMessage.cs │ │ ├── QueueRemoveMessage.cs │ │ ├── LoadFailedMessage.cs │ │ ├── QueueReorderMessage.cs │ │ ├── LoadCancelledMessage.cs │ │ ├── QueueItemIdsMessage.cs │ │ ├── MediaSessionMessage.cs │ │ ├── QueueItemsMessage.cs │ │ ├── SetPlaybackRateMessage.cs │ │ ├── SeekMessage.cs │ │ ├── InvalidRequestMessage.cs │ │ ├── GetQueueItemsMessage .cs │ │ ├── QueueInsertMessage.cs │ │ ├── QueueUpdateMessage.cs │ │ ├── QueueChangeMessage.cs │ │ ├── LoadMessage.cs │ │ ├── EditTracksInfoMessage.cs │ │ └── QueueLoadMessage.cs │ ├── IMessage.cs │ ├── ReceptionMessageAttribute.cs │ ├── IMessageWithId.cs │ ├── IStatusMessage.cs │ ├── SessionMessage.cs │ ├── StatusMessage.cs │ ├── Message.cs │ └── MessageWithId.cs ├── ProtocolVersion.cs ├── IMessageTypes.cs ├── PayloadType.cs ├── Models │ ├── Receiver │ │ ├── Namespace.cs │ │ ├── ReceiverStatus.cs │ │ └── Application.cs │ ├── Media │ │ ├── TrackType.cs │ │ ├── StreamType.cs │ │ ├── TextTrackWindowType.cs │ │ ├── MetadataType.cs │ │ ├── TextTrackFontStyle.cs │ │ ├── MovieMetadata.cs │ │ ├── TextTrackEdgeType.cs │ │ ├── QueueChangeType.cs │ │ ├── TextTrackFontGenericFamily.cs │ │ ├── QueueStatus.cs │ │ ├── RepeatMode.cs │ │ ├── TextTrackType.cs │ │ ├── MusicTrackMediaMetadata.cs │ │ ├── GenericMediaMetadata.cs │ │ ├── QueueItem.cs │ │ ├── Track.cs │ │ ├── MediaInformation.cs │ │ ├── MediaStatus.cs │ │ └── TextTrackStyle.cs │ ├── Image.cs │ └── Volume.cs ├── IReceiver.cs ├── DefaultIdentifiers.cs ├── IDeviceLocator.cs ├── ServiceCollectionExtensions.cs ├── MessageTypes.cs ├── Receiver.cs ├── TaskExtensions.cs ├── DeviceLocator.cs ├── CastMessage.cs ├── ColorHelper.cs ├── GoogleCast.csproj ├── StringExtensions.cs ├── EnumHelper.cs ├── JsonSerializer.cs └── ISender.cs ├── MBCCRules ├── MBCCRules.csproj ├── app.manifest └── Program.cs ├── Properties ├── AssemblyInfo.cs ├── Resources.Designer.cs └── Resources.resx ├── app.config ├── packages.config ├── Settings.cs ├── WebServer.cs ├── ChromecastPanel.Designer.cs ├── CSharp.sln ├── README.md ├── CAF Receiver ├── css │ └── style.css ├── build │ └── receiver.html └── receiver.html ├── ChromecastPanel.cs ├── Settings.Designer.cs ├── Settings.resx ├── ChromecastPanel.resx └── .gitignore /Images/UI.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TroyFernandes/MusicBeeChromecast/HEAD/Images/UI.jpg -------------------------------------------------------------------------------- /GoogleCast/Channels/IHeartbeatChannel.cs: -------------------------------------------------------------------------------- 1 | namespace GoogleCast.Channels 2 | { 3 | /// 4 | /// Interface for the heartbeat channel 5 | /// 6 | interface IHeartbeatChannel : IChannel 7 | { 8 | } 9 | } -------------------------------------------------------------------------------- /GoogleCast/Channels/INextMessageChannel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace GoogleCast.Channels 6 | { 7 | interface INextMessageChannel: IChannel 8 | { 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /GoogleCast/Channels/IPreviousMessageChannel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace GoogleCast.Channels 6 | { 7 | public interface IPreviousMessageChannel: IChannel 8 | { 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /GoogleCast/Messages/Hearbeat/PongMessage.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.Serialization; 2 | 3 | namespace GoogleCast.Messages.Heartbeat 4 | { 5 | /// 6 | /// Pong message 7 | /// 8 | [DataContract] 9 | class PongMessage : Message 10 | { 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /GoogleCast/Messages/Receiver/StopMessage.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.Serialization; 2 | 3 | namespace GoogleCast.Messages.Receiver 4 | { 5 | /// 6 | /// Stop message 7 | /// 8 | [DataContract] 9 | class StopMessage : SessionMessage 10 | { 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /GoogleCast/ProtocolVersion.cs: -------------------------------------------------------------------------------- 1 | namespace GoogleCast 2 | { 3 | /// 4 | /// Protocol version 5 | /// 6 | enum ProtocolVersion 7 | { 8 | /// 9 | /// CastV2_1_0 10 | /// 11 | CastV2_1_0 = 0 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /GoogleCast/Messages/Connection/ConnectMessage.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.Serialization; 2 | 3 | namespace GoogleCast.Messages.Connection 4 | { 5 | /// 6 | /// Connect message 7 | /// 8 | [DataContract] 9 | class ConnectMessage : Message 10 | { 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /GoogleCast/IMessageTypes.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace GoogleCast 5 | { 6 | /// 7 | /// Interface for message types dictionary 8 | /// 9 | public interface IMessageTypes : IDictionary 10 | { 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /GoogleCast/Messages/Media/QueueMessage.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.Serialization; 2 | 3 | namespace GoogleCast.Messages.Media 4 | { 5 | /// 6 | /// Queue message 7 | /// 8 | [DataContract] 9 | abstract class QueueMessage : MediaSessionMessage 10 | { 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /GoogleCast/Messages/Receiver/GetStatusMessage.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.Serialization; 2 | 3 | namespace GoogleCast.Messages.Receiver 4 | { 5 | /// 6 | /// Get status message 7 | /// 8 | [DataContract] 9 | class GetStatusMessage : MessageWithId 10 | { 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /GoogleCast/Messages/Hearbeat/PingMessage.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.Serialization; 2 | 3 | namespace GoogleCast.Messages.Heartbeat 4 | { 5 | /// 6 | /// Ping message 7 | /// 8 | [DataContract] 9 | [ReceptionMessage] 10 | class PingMessage : Message 11 | { 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /GoogleCast/Messages/Connection/CloseMessage.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.Serialization; 2 | 3 | namespace GoogleCast.Messages.Connection 4 | { 5 | /// 6 | /// Close message 7 | /// 8 | [DataContract] 9 | [ReceptionMessage] 10 | class CloseMessage : Message 11 | { 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /GoogleCast/Messages/Media/GetStatusMessage.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.Serialization; 2 | 3 | namespace GoogleCast.Messages.Media 4 | { 5 | /// 6 | /// Message to retrieve the media status 7 | /// 8 | [DataContract] 9 | class GetStatusMessage : MediaSessionMessage 10 | { 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /GoogleCast/Messages/Media/StopMessage.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.Serialization; 2 | 3 | namespace GoogleCast.Messages.Media 4 | { 5 | /// 6 | /// Message to stop playback of the current content 7 | /// 8 | [DataContract] 9 | class StopMessage : MediaSessionMessage 10 | { 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /GoogleCast/Messages/IMessage.cs: -------------------------------------------------------------------------------- 1 | namespace GoogleCast.Messages 2 | { 3 | /// 4 | /// Interface for a message 5 | /// 6 | public interface IMessage 7 | { 8 | /// 9 | /// Gets the message type 10 | /// 11 | string Type { get; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /GoogleCast/Messages/Media/PauseMessage.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.Serialization; 2 | 3 | namespace GoogleCast.Messages.Media 4 | { 5 | /// 6 | /// Message to pause playback of the current content 7 | /// 8 | [DataContract] 9 | class PauseMessage : MediaSessionMessage 10 | { 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /GoogleCast/Channels/NextMessageChannel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace GoogleCast.Channels 6 | { 7 | class NextMessageChannel : Channel, INextMessageChannel 8 | { 9 | public NextMessageChannel() : base("next") 10 | { 11 | 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /GoogleCast/Messages/Media/SkipForwardMessage.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.Serialization; 2 | 3 | namespace GoogleCast.Messages.Media 4 | { 5 | /// 6 | /// Message to skip to the next item in the queue 7 | /// 8 | [DataContract] 9 | class QueueNextMessage : MediaSessionMessage 10 | { 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /GoogleCast/Messages/Media/SkipBackwardMessage.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.Serialization; 2 | 3 | namespace GoogleCast.Messages.Media 4 | { 5 | /// 6 | /// Message to skip to the previous item in the queue 7 | /// 8 | [DataContract] 9 | class QueuePrevMessage : MediaSessionMessage 10 | { 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /GoogleCast/Messages/ReceptionMessageAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace GoogleCast.Messages 4 | { 5 | /// 6 | /// Attribute for received messages 7 | /// 8 | [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] 9 | public class ReceptionMessageAttribute : Attribute 10 | { 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /GoogleCast/Messages/Media/PlayMessage.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.Serialization; 2 | 3 | namespace GoogleCast.Messages.Media 4 | { 5 | /// 6 | /// Message to begin playback of the content that was loaded with the load call 7 | /// 8 | [DataContract] 9 | class PlayMessage : MediaSessionMessage 10 | { 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /GoogleCast/Channels/PreviousMessageChannel.cs: -------------------------------------------------------------------------------- 1 | using GoogleCast.Messages; 2 | using System; 3 | using System.Threading.Tasks; 4 | 5 | namespace GoogleCast.Channels 6 | { 7 | class PreviousMessageChannel : Channel, IPreviousMessageChannel 8 | { 9 | public PreviousMessageChannel() : base("previous") 10 | { 11 | } 12 | 13 | 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /GoogleCast/PayloadType.cs: -------------------------------------------------------------------------------- 1 | namespace GoogleCast 2 | { 3 | /// 4 | /// Payload type 5 | /// 6 | enum PayloadType 7 | { 8 | /// 9 | /// String 10 | /// 11 | String = 0, 12 | 13 | /// 14 | /// Binary 15 | /// 16 | Binary = 1 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /GoogleCast/Messages/IMessageWithId.cs: -------------------------------------------------------------------------------- 1 | namespace GoogleCast.Messages 2 | { 3 | /// 4 | /// Interface for messages with request identifier 5 | /// 6 | public interface IMessageWithId : IMessage 7 | { 8 | /// 9 | /// Gets the request identifier 10 | /// 11 | int RequestId { get; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /GoogleCast/Channels/IApplicationChannel.cs: -------------------------------------------------------------------------------- 1 | namespace GoogleCast.Channels 2 | { 3 | /// 4 | /// Interface for application channels 5 | /// 6 | public interface IApplicationChannel : IChannel 7 | { 8 | /// 9 | /// Gets the application identifier 10 | /// 11 | string ApplicationId { get; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /GoogleCast/Messages/Media/NextMessage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Runtime.Serialization; 4 | using System.Text; 5 | 6 | namespace GoogleCast.Messages.Media 7 | { /// 8 | /// Chromecast Next Command 9 | /// 10 | [DataContract] 11 | [ReceptionMessage] 12 | class NextMessage : Message 13 | { 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /GoogleCast/Messages/Media/GetQueueItemIdsMessage.cs: -------------------------------------------------------------------------------- 1 | using GoogleCast.Messages.Media; 2 | using System.Runtime.Serialization; 3 | 4 | namespace GoogleCast.Messages.Media 5 | { 6 | /// 7 | /// A request to retrieve all of the item ids currently in the queue 8 | /// 9 | [DataContract] 10 | class QueueGetItemIdsMessage : MediaSessionMessage 11 | { 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /GoogleCast/Messages/Media/PreviousMessage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Runtime.Serialization; 4 | using System.Text; 5 | 6 | namespace GoogleCast.Messages.Media 7 | 8 | { /// 9 | /// Custom Message 10 | /// 11 | [DataContract] 12 | [ReceptionMessage] 13 | class PreviousMessage : Message 14 | { 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /GoogleCast/Messages/Receiver/ReceiverStatusMessage.cs: -------------------------------------------------------------------------------- 1 | using GoogleCast.Models.Receiver; 2 | using System.Runtime.Serialization; 3 | 4 | namespace GoogleCast.Messages.Receiver 5 | { 6 | /// 7 | /// Receiver status message 8 | /// 9 | [DataContract] 10 | [ReceptionMessage] 11 | class ReceiverStatusMessage : StatusMessage 12 | { 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /GoogleCast/Messages/IStatusMessage.cs: -------------------------------------------------------------------------------- 1 | namespace GoogleCast.Messages 2 | { 3 | /// 4 | /// Interface for status messages 5 | /// 6 | /// status type 7 | public interface IStatusMessage : IMessageWithId 8 | { 9 | /// 10 | /// Gets the status 11 | /// 12 | TStatus Status { get; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /GoogleCast/Messages/Media/MediaStatusMessage.cs: -------------------------------------------------------------------------------- 1 | using GoogleCast.Models.Media; 2 | using System.Collections.Generic; 3 | using System.Runtime.Serialization; 4 | 5 | namespace GoogleCast.Messages.Media 6 | { 7 | /// 8 | /// Message to retrieve the media status 9 | /// 10 | [DataContract] 11 | [ReceptionMessage] 12 | class MediaStatusMessage : StatusMessage> 13 | { 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /GoogleCast/Models/Receiver/Namespace.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.Serialization; 2 | 3 | namespace GoogleCast.Models.Receiver 4 | { 5 | /// 6 | /// Namespace 7 | /// 8 | [DataContract] 9 | public class Namespace 10 | { 11 | /// 12 | /// Gets or sets the name 13 | /// 14 | [DataMember(Name = "name")] 15 | public string Name { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /GoogleCast/IReceiver.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | 3 | namespace GoogleCast 4 | { 5 | /// 6 | /// Interface for a receiver 7 | /// 8 | public interface IReceiver 9 | { 10 | /// 11 | /// Gets the friendly name 12 | /// 13 | string FriendlyName { get; } 14 | 15 | /// 16 | /// Gets the network endpoint 17 | /// 18 | IPEndPoint IPEndPoint { get; } 19 | } 20 | } -------------------------------------------------------------------------------- /GoogleCast/Models/Media/TrackType.cs: -------------------------------------------------------------------------------- 1 | namespace GoogleCast.Models.Media 2 | { 3 | /// 4 | /// Track type 5 | /// 6 | public enum TrackType 7 | { 8 | /// 9 | /// Text 10 | /// 11 | Text, 12 | 13 | /// 14 | /// Audio 15 | /// 16 | Audio, 17 | 18 | /// 19 | /// Video 20 | /// 21 | Video 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /GoogleCast/Messages/Media/PositionChangeMessage.cs: -------------------------------------------------------------------------------- 1 | using GoogleCast.Models.Media; 2 | using System.Runtime.Serialization; 3 | 4 | namespace GoogleCast.Messages.Media 5 | { 6 | /// 7 | /// Message to retrieve all changed queued item ids 8 | /// 9 | [DataContract] 10 | [ReceptionMessage] 11 | class PositionChangeMessage : MessageWithId 12 | { 13 | [DataMember(Name = "position")] 14 | public double Position { get; set; } 15 | 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /GoogleCast/Messages/Receiver/LaunchMessage.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.Serialization; 2 | 3 | namespace GoogleCast.Messages.Receiver 4 | { 5 | /// 6 | /// Launch message 7 | /// 8 | [DataContract] 9 | class LaunchMessage : MessageWithId 10 | { 11 | /// 12 | /// Gets or sets the application identifier 13 | /// 14 | [DataMember(Name = "appId")] 15 | public string ApplicationId { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /GoogleCast/Models/Media/StreamType.cs: -------------------------------------------------------------------------------- 1 | namespace GoogleCast.Models.Media 2 | { 3 | /// 4 | /// Stream type 5 | /// 6 | public enum StreamType 7 | { 8 | /// 9 | /// None 10 | /// 11 | None, 12 | 13 | /// 14 | /// Live 15 | /// 16 | Live, 17 | 18 | /// 19 | /// Buffered 20 | /// 21 | Buffered 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /GoogleCast/Messages/SessionMessage.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.Serialization; 2 | 3 | namespace GoogleCast.Messages 4 | { 5 | /// 6 | /// Session message base class 7 | /// 8 | [DataContract] 9 | public abstract class SessionMessage : MessageWithId 10 | { 11 | /// 12 | /// Gets or sets the session identifier 13 | /// 14 | [DataMember(Name = "sessionId")] 15 | public string SessionId { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /GoogleCast/Messages/Receiver/SetVolumeMessage.cs: -------------------------------------------------------------------------------- 1 | using GoogleCast.Models; 2 | using System.Runtime.Serialization; 3 | 4 | namespace GoogleCast.Messages.Receiver 5 | { 6 | /// 7 | /// Volume Message 8 | /// 9 | [DataContract] 10 | class SetVolumeMessage : MessageWithId 11 | { 12 | /// 13 | /// Gets or sets the volume 14 | /// 15 | [DataMember(Name = "volume")] 16 | public Volume Volume { get; set; } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /GoogleCast/DefaultIdentifiers.cs: -------------------------------------------------------------------------------- 1 | namespace GoogleCast 2 | { 3 | /// 4 | /// Default identifiers 5 | /// 6 | static class DefaultIdentifiers 7 | { 8 | /// 9 | /// Default sender identifier 10 | /// 11 | public const string SENDER_ID = "sender-0"; 12 | 13 | /// 14 | /// Default destination identifier 15 | /// 16 | public const string DESTINATION_ID = "receiver-0"; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /GoogleCast/Messages/Media/QueueRemoveMessage.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.Serialization; 2 | 3 | namespace GoogleCast.Messages.Media 4 | { 5 | /// 6 | /// Message to remove given queued item ids 7 | /// 8 | [DataContract] 9 | class QueueRemoveMessage : QueueMessage 10 | { 11 | /// 12 | /// Gets or sets the item id array 13 | /// 14 | [DataMember(Name = "itemIds")] 15 | public int[] ItemIds { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /GoogleCast/Channels/IConnectionChannel.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace GoogleCast.Channels 4 | { 5 | /// 6 | /// Interface for the connection channel 7 | /// 8 | public interface IConnectionChannel : IChannel 9 | { 10 | /// 11 | /// Connects 12 | /// 13 | /// destination identifier 14 | Task ConnectAsync(string destinationId = DefaultIdentifiers.DESTINATION_ID); 15 | } 16 | } -------------------------------------------------------------------------------- /GoogleCast/Messages/Media/LoadFailedMessage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | 4 | namespace GoogleCast.Messages.Media 5 | { 6 | /// 7 | /// Load failed message 8 | /// 9 | [DataContract] 10 | [ReceptionMessage] 11 | class LoadFailedMessage : MessageWithId 12 | { 13 | [OnDeserializing] 14 | private void OnDeserializing(StreamingContext context) 15 | { 16 | throw new Exception("Load failed"); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /GoogleCast/Messages/Media/QueueReorderMessage.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.Serialization; 2 | 3 | namespace GoogleCast.Messages.Media 4 | { 5 | /// 6 | /// Message to reorder the given queued item ids 7 | /// 8 | [DataContract] 9 | class QueueReorderMessage : QueueMessage 10 | { 11 | /// 12 | /// Gets or sets the item id array 13 | /// 14 | [DataMember(Name = "itemIds")] 15 | public int[] ItemIds { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /GoogleCast/Messages/Media/LoadCancelledMessage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | 4 | namespace GoogleCast.Messages.Media 5 | { 6 | /// 7 | /// Load cancelled message 8 | /// 9 | [DataContract] 10 | [ReceptionMessage] 11 | class LoadCancelledMessage : MessageWithId 12 | { 13 | [OnDeserializing] 14 | private void OnDeserializing(StreamingContext context) 15 | { 16 | throw new Exception("Load cancelled"); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /GoogleCast/Models/Media/TextTrackWindowType.cs: -------------------------------------------------------------------------------- 1 | namespace GoogleCast.Models.Media 2 | { 3 | /// 4 | /// Text track window types 5 | /// 6 | public enum TextTrackWindowType 7 | { 8 | /// 9 | /// None 10 | /// 11 | None, 12 | 13 | /// 14 | /// Normal 15 | /// 16 | Normal, 17 | 18 | /// 19 | /// Rounded corners 20 | /// 21 | RoundedCorners 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /GoogleCast/Messages/Media/QueueItemIdsMessage.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.Serialization; 2 | 3 | namespace GoogleCast.Messages.Media 4 | { 5 | /// 6 | /// Message to retrieve all queued item ids 7 | /// 8 | [DataContract] 9 | [ReceptionMessage] 10 | class QueueItemIdsMessage : MessageWithId 11 | { 12 | /// 13 | /// Gets or sets the item id array 14 | /// 15 | [DataMember(Name = "itemIds")] 16 | public int[] ItemIds { get; set; } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /GoogleCast/Messages/Media/MediaSessionMessage.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.Serialization; 2 | 3 | namespace GoogleCast.Messages.Media 4 | { 5 | /// 6 | /// Media session message 7 | /// 8 | [DataContract] 9 | abstract class MediaSessionMessage : MessageWithId 10 | { 11 | /// 12 | /// Gets or sets the media session identifier 13 | /// 14 | [DataMember(Name = "mediaSessionId", EmitDefaultValue = false)] 15 | public long? MediaSessionId { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /GoogleCast/Messages/Media/QueueItemsMessage.cs: -------------------------------------------------------------------------------- 1 | using GoogleCast.Models.Media; 2 | using System.Runtime.Serialization; 3 | 4 | namespace GoogleCast.Messages.Media 5 | { 6 | /// 7 | /// Message to retrieve all queued item ids 8 | /// 9 | [DataContract] 10 | [ReceptionMessage] 11 | class QueueItemsMessage : MessageWithId 12 | { 13 | /// 14 | /// Gets or sets the item id array 15 | /// 16 | [DataMember(Name = "items")] 17 | public QueueItem[] Items { get; set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /GoogleCast/Messages/StatusMessage.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.Serialization; 2 | 3 | namespace GoogleCast.Messages 4 | { 5 | /// 6 | /// Status message base class 7 | /// 8 | /// status type 9 | [DataContract] 10 | public abstract class StatusMessage : MessageWithId, IStatusMessage 11 | { 12 | /// 13 | /// Gets or sets the status 14 | /// 15 | [DataMember(Name = "status")] 16 | public TStatus Status { get; set; } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /GoogleCast/Models/Media/MetadataType.cs: -------------------------------------------------------------------------------- 1 | namespace GoogleCast.Models.Media 2 | { 3 | /// 4 | /// Metadata type 5 | /// 6 | public enum MetadataType 7 | { 8 | /// 9 | /// Default 10 | /// 11 | Default = 0, 12 | 13 | /// 14 | /// Movie 15 | /// 16 | Movie = 2, 17 | 18 | /// 19 | /// Music 20 | /// 21 | Music = 3, 22 | 23 | /// 24 | /// Photo 25 | /// 26 | Photo = 4 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /GoogleCast/Channels/IStatusChannel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace GoogleCast.Channels 5 | { 6 | /// 7 | /// Interface for a status channel 8 | /// 9 | /// status type 10 | public interface IStatusChannel : IChannel 11 | { 12 | /// 13 | /// Raised when the status has changed 14 | /// 15 | event EventHandler StatusChanged; 16 | 17 | /// 18 | /// Gets the status 19 | /// 20 | TStatus Status { get; } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /MBCCRules/MBCCRules.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net461 6 | app.manifest 7 | 8 | 9 | 10 | 11 | 58fbcf7c-e7a9-467c-80b3-fc65e8fcca08 12 | 1 13 | 0 14 | 0 15 | tlbimp 16 | false 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /GoogleCast/Models/Media/TextTrackFontStyle.cs: -------------------------------------------------------------------------------- 1 | namespace GoogleCast.Models.Media 2 | { 3 | /// 4 | /// Possible text track font styles 5 | /// 6 | public enum TextTrackFontStyle 7 | { 8 | /// 9 | /// Normal 10 | /// 11 | Normal, 12 | 13 | /// 14 | /// Bold 15 | /// 16 | Bold, 17 | 18 | /// 19 | /// Bold italic 20 | /// 21 | BoldItalic, 22 | 23 | /// 24 | /// Italic 25 | /// 26 | Italic 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /GoogleCast/Messages/Media/SetPlaybackRateMessage.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.Serialization; 2 | 3 | namespace GoogleCast.Messages.Media 4 | { 5 | /// 6 | /// Message to set the current playback rate of the stream 7 | /// 8 | [DataContract] 9 | class SetPlaybackRateMessage : MediaSessionMessage 10 | { 11 | /// 12 | /// Gets or sets the relative playback rate 13 | /// 14 | /// the given playback will affect the current playback rate 15 | [DataMember(Name = "playbackRate")] 16 | public double PlaybackRate { get; set; } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /GoogleCast/Messages/Media/SeekMessage.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.Serialization; 2 | 3 | namespace GoogleCast.Messages.Media 4 | { 5 | /// 6 | /// Message to set the current position in the stream 7 | /// 8 | [DataContract] 9 | class SeekMessage : MediaSessionMessage 10 | { 11 | /// 12 | /// Gets or sets the seconds since beginning of content 13 | /// 14 | /// if the content is live content, and position is not specifed, the stream will start at the live position 15 | [DataMember(Name = "currentTime")] 16 | public double CurrentTime { get; set; } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /GoogleCast/Messages/Media/InvalidRequestMessage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | 4 | namespace GoogleCast.Messages.Media 5 | { 6 | /// 7 | /// Invalid request message 8 | /// 9 | [DataContract] 10 | [ReceptionMessage] 11 | class InvalidRequestMessage : MessageWithId 12 | { 13 | /// 14 | /// Gets or sets the reason 15 | /// 16 | [DataMember(Name = "reason")] 17 | public string Reason { get; set; } 18 | 19 | [OnDeserialized] 20 | private void OnDeserialized(StreamingContext context) 21 | { 22 | throw new InvalidOperationException(Reason); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /GoogleCast/Models/Media/MovieMetadata.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.Serialization; 2 | 3 | namespace GoogleCast.Models.Media 4 | { 5 | /// 6 | /// Movie metadata 7 | /// 8 | [DataContract] 9 | public class MovieMetadata : GenericMediaMetadata 10 | { 11 | /// 12 | /// Initializes a new instance of class 13 | /// 14 | public MovieMetadata() 15 | { 16 | MetadataType = MetadataType.Movie; 17 | } 18 | 19 | /// 20 | /// Gets or sets the studio 21 | /// 22 | [DataMember(Name = "studio")] 23 | public string Studio { get; set; } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /GoogleCast/Messages/Media/GetQueueItemsMessage .cs: -------------------------------------------------------------------------------- 1 | using GoogleCast.Models.Media; 2 | using System.Collections.Generic; 3 | using System.Runtime.Serialization; 4 | 5 | namespace GoogleCast.Messages.Media 6 | { 7 | /// 8 | /// A request to to retrieve the information of the given list of queue item ids 9 | /// 10 | [DataContract] 11 | class QueueGetItemsMessage : MediaSessionMessage 12 | { 13 | /// 14 | /// Gets or sets the array of item ids of which to retrieve the info 15 | /// 16 | /// must not be null or empty 17 | [DataMember(Name = "itemIds")] 18 | public IEnumerable ItemIds { get; set; } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /GoogleCast/Models/Receiver/ReceiverStatus.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Runtime.Serialization; 3 | 4 | namespace GoogleCast.Models.Receiver 5 | { 6 | /// 7 | /// Receiver status 8 | /// 9 | [DataContract] 10 | public class ReceiverStatus 11 | { 12 | /// 13 | /// Gets or sets the applications collection 14 | /// 15 | [DataMember(Name = "applications")] 16 | public IEnumerable Applications { get; set; } 17 | 18 | /// 19 | /// Gets or sets the volume 20 | /// 21 | [DataMember(Name = "volume")] 22 | public Volume Volume { get; set; } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /GoogleCast/Models/Media/TextTrackEdgeType.cs: -------------------------------------------------------------------------------- 1 | namespace GoogleCast.Models.Media 2 | { 3 | /// 4 | /// Possible text track edge types 5 | /// 6 | public enum TextTrackEdgeType 7 | { 8 | /// 9 | /// None 10 | /// 11 | None, 12 | 13 | /// 14 | /// Outline 15 | /// 16 | Outline, 17 | 18 | /// 19 | /// Drop shadow 20 | /// 21 | DropShadow, 22 | 23 | /// 24 | /// Raised 25 | /// 26 | Raised, 27 | 28 | /// 29 | /// Depressed 30 | /// 31 | Depressed 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /GoogleCast/Models/Media/QueueChangeType.cs: -------------------------------------------------------------------------------- 1 | namespace GoogleCast.Models.Media 2 | { 3 | /// 4 | /// Queue change type 5 | /// 6 | public enum QueueChangeType 7 | { 8 | /// 9 | /// Insert 10 | /// 11 | Insert = 0, 12 | 13 | /// 14 | /// Remove 15 | /// 16 | Remove = 1, 17 | 18 | /// 19 | /// Items Change 20 | /// 21 | ItemsChange = 2, 22 | 23 | /// 24 | /// Update 25 | /// 26 | Update = 3, 27 | 28 | /// 29 | /// No Change 30 | /// 31 | NoChange = 4 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /GoogleCast/IDeviceLocator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | 5 | namespace GoogleCast 6 | { 7 | /// 8 | /// Interface for the device locator 9 | /// 10 | public interface IDeviceLocator 11 | { 12 | /// 13 | /// Finds the available receivers 14 | /// 15 | /// a collection of receivers 16 | Task> FindReceiversAsync(); 17 | 18 | /// 19 | /// Finds the available receivers in continuous way 20 | /// 21 | /// a provider for notifications 22 | IObservable FindReceiversContinuous(); 23 | } 24 | } -------------------------------------------------------------------------------- /GoogleCast/Messages/Media/QueueInsertMessage.cs: -------------------------------------------------------------------------------- 1 | using GoogleCast.Models.Media; 2 | using System.Collections.Generic; 3 | using System.Runtime.Serialization; 4 | 5 | namespace GoogleCast.Messages.Media 6 | { 7 | /// 8 | /// A request to load and optionally start playback of a new ordered list of media items 9 | /// 10 | [DataContract] 11 | class QueueInsertMessage : QueueMessage 12 | { 13 | /// 14 | /// Gets or sets the array of items to insert into the queue. It is sorted (first element will be played first) 15 | /// 16 | /// must not be null or empty 17 | [DataMember(Name = "items")] 18 | public IEnumerable Items { get; set; } 19 | } 20 | } -------------------------------------------------------------------------------- /GoogleCast/Channels/IChannel.cs: -------------------------------------------------------------------------------- 1 | using GoogleCast.Messages; 2 | using System.Threading.Tasks; 3 | 4 | namespace GoogleCast.Channels 5 | { 6 | /// 7 | /// Interface for a channel 8 | /// 9 | public interface IChannel 10 | { 11 | /// 12 | /// Gets or sets the sender 13 | /// 14 | ISender Sender { get; set; } 15 | 16 | /// 17 | /// Gets the full namespace 18 | /// 19 | string Namespace { get; } 20 | 21 | /// 22 | /// Called when a message for this channel is received 23 | /// 24 | /// message to process 25 | Task OnMessageReceivedAsync(IMessage message); 26 | } 27 | } -------------------------------------------------------------------------------- /GoogleCast/Messages/Media/QueueUpdateMessage.cs: -------------------------------------------------------------------------------- 1 | using GoogleCast.Models.Media; 2 | using System.Runtime.Serialization; 3 | 4 | namespace GoogleCast.Messages.Media 5 | { 6 | /// 7 | /// Message to update the queue 8 | /// 9 | [DataContract] 10 | class QueueUpdateMessage : MediaSessionMessage 11 | { 12 | /// 13 | /// Gets or sets the item id of the currently playing media 14 | /// 15 | [DataMember(Name = "currentItemId")] 16 | public int? CurrentItemId { get; set; } 17 | 18 | /// 19 | /// Gets or sets the shuffle state 20 | /// 21 | [DataMember(Name = "shuffle")] 22 | public bool? Shuffle { get; set; } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /GoogleCast/Messages/Media/QueueChangeMessage.cs: -------------------------------------------------------------------------------- 1 | using GoogleCast.Models.Media; 2 | using System.Runtime.Serialization; 3 | 4 | namespace GoogleCast.Messages.Media 5 | { 6 | /// 7 | /// Message to retrieve all changed queued item ids 8 | /// 9 | [DataContract] 10 | [ReceptionMessage] 11 | class QueueChangeMessage : MessageWithId 12 | { 13 | /// 14 | /// Gets or sets the item id array 15 | /// 16 | [DataMember(Name = "itemIds")] 17 | public int[] ItemIds { get; set; } 18 | 19 | /// 20 | /// Gets or sets the item id array 21 | /// 22 | [DataMember(Name = "changeType")] 23 | public string ChangeType { get; set; } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /GoogleCast/Models/Image.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.Serialization; 2 | 3 | namespace GoogleCast.Models 4 | { 5 | /// 6 | /// Image 7 | /// 8 | [DataContract] 9 | public class Image 10 | { 11 | /// 12 | /// Gets or sets the URI for the image 13 | /// 14 | [DataMember(Name = "url")] 15 | public string Url { get; set; } 16 | 17 | /// 18 | /// Gets or sets the height of the image 19 | /// 20 | [DataMember(Name = "height")] 21 | public int? Height { get; set; } 22 | 23 | /// 24 | /// Gets or sets the width of the image 25 | /// 26 | [DataMember(Name = "width")] 27 | public int? Width { get; set; } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /GoogleCast/Models/Volume.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.Serialization; 2 | 3 | namespace GoogleCast.Models 4 | { 5 | /// 6 | /// Volume 7 | /// 8 | [DataContract] 9 | public class Volume 10 | { 11 | /// 12 | /// Gets or sets the volume level 13 | /// 14 | [DataMember(Name = "level", EmitDefaultValue = false)] 15 | public float? Level { get; set; } 16 | 17 | /// 18 | /// Gets or sets a value indicating whether the audio is muted 19 | /// 20 | [DataMember(Name = "muted", EmitDefaultValue = false)] 21 | public bool? IsMuted { get; set; } 22 | 23 | /// 24 | /// Gets or sets the step interval 25 | /// 26 | [DataMember(Name = "stepInterval")] 27 | public float StepInterval { get; set; } 28 | } 29 | } -------------------------------------------------------------------------------- /GoogleCast/Models/Media/TextTrackFontGenericFamily.cs: -------------------------------------------------------------------------------- 1 | namespace GoogleCast.Models.Media 2 | { 3 | /// 4 | /// Text track font generic family 5 | /// 6 | public enum TextTrackFontGenericFamily 7 | { 8 | /// 9 | /// Sans serif 10 | /// 11 | SansSerif, 12 | 13 | /// 14 | /// Monospaced sans serif 15 | /// 16 | MonospacedSansSerif, 17 | 18 | /// 19 | /// Serif 20 | /// 21 | Serif, 22 | 23 | /// 24 | /// Monospaced serif 25 | /// 26 | MonospacedSerif, 27 | 28 | /// 29 | /// Casual 30 | /// 31 | Casual, 32 | 33 | /// 34 | /// Cursive 35 | /// 36 | Cursive, 37 | 38 | /// 39 | /// Small capitals 40 | /// 41 | SmallCapitals 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /GoogleCast/Messages/Message.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | 4 | namespace GoogleCast.Messages 5 | { 6 | /// 7 | /// Message base class 8 | /// 9 | [DataContract] 10 | public abstract class Message : IMessage 11 | { 12 | /// 13 | /// Initialization 14 | /// 15 | public Message() 16 | { 17 | Type = GetMessageType(GetType()); 18 | } 19 | 20 | /// 21 | /// Gets the message type 22 | /// 23 | [DataMember(Name = "type")] 24 | public string Type { get; set; } 25 | 26 | /// 27 | /// Gets the message type 28 | /// 29 | /// message class type 30 | public static string GetMessageType(Type type) 31 | { 32 | var typeName = type.Name; 33 | return typeName.Substring(0, typeName.LastIndexOf(nameof(Message))).ToUnderscoreUpperInvariant(); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /GoogleCast/Channels/HeartbeatChannel.cs: -------------------------------------------------------------------------------- 1 | using GoogleCast.Messages; 2 | using GoogleCast.Messages.Heartbeat; 3 | using System.Threading.Tasks; 4 | 5 | namespace GoogleCast.Channels 6 | { 7 | /// 8 | /// Heartbeat channel 9 | /// 10 | class HeartbeatChannel : Channel, IHeartbeatChannel 11 | { 12 | /// 13 | /// Initializes a new instance of class 14 | /// 15 | public HeartbeatChannel() : base("tp.heartbeat") 16 | { 17 | } 18 | 19 | /// 20 | /// Called when a message for this channel is received 21 | /// 22 | /// message to process 23 | public override async Task OnMessageReceivedAsync(IMessage message) 24 | { 25 | switch (message) 26 | { 27 | case PingMessage pingMessage: 28 | await SendAsync(new PongMessage()); 29 | break; 30 | } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /GoogleCast/Messages/MessageWithId.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | using System.Threading; 4 | 5 | namespace GoogleCast.Messages 6 | { 7 | /// 8 | /// Message with request identifier 9 | /// 10 | [DataContract] 11 | public class MessageWithId : Message, IMessageWithId 12 | { 13 | private static int _id = new Random().Next(); 14 | 15 | /// 16 | /// Gets a value indicating whether the message has a request identifier 17 | /// 18 | public bool HasRequestId 19 | { 20 | get { return _requestId != null; } 21 | } 22 | 23 | private int? _requestId; 24 | /// 25 | /// Gets or sets the request identifier 26 | /// 27 | [DataMember(Name = "requestId")] 28 | public int RequestId 29 | { 30 | get { return (int)(_requestId ?? (_requestId = Interlocked.Increment(ref _id))); } 31 | set { _requestId = value; } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /GoogleCast/Models/Media/QueueStatus.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Runtime.Serialization; 3 | 4 | namespace GoogleCast.Models.Media 5 | { 6 | /// 7 | /// Queue status 8 | /// 9 | [DataContract] 10 | public class QueueStatus 11 | { 12 | /// 13 | /// Gets or sets the item id array 14 | /// 15 | [DataMember(Name = "itemIds")] 16 | public int[] ItemIds { get; set; } 17 | 18 | /// 19 | /// Gets or sets the type of media artifact 20 | /// 21 | [IgnoreDataMember] 22 | public QueueChangeType ChangeType { get; set; } 23 | 24 | /// 25 | /// Gets or sets the item id array 26 | /// 27 | [DataMember(Name = "changeType")] 28 | public string ChangeTypeString 29 | { 30 | get { return ChangeType.GetName(); } 31 | set { ChangeType = EnumHelper.Parse(value); } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /GoogleCast/Models/Media/RepeatMode.cs: -------------------------------------------------------------------------------- 1 | namespace GoogleCast.Models.Media 2 | { 3 | /// 4 | /// Possible states of queue repeat mode 5 | /// 6 | public enum RepeatMode 7 | { 8 | /// 9 | /// Items are played in order, and when the queue is completed (the last item has ended) the media session is terminated 10 | /// 11 | RepeatOff, 12 | 13 | /// 14 | /// The items in the queue will be played indefinitely. When the last item has ended, the first item will be played again 15 | /// 16 | RepeatAll, 17 | 18 | /// 19 | /// The current item will be repeated indefinitely 20 | /// 21 | RepeatSingle, 22 | 23 | /// 24 | /// The items in the queue will be played indefinitely. 25 | /// When the last item has ended, the list of items will be randomly shuffled by the receiver, 26 | /// and the queue will continue to play starting from the first item of the shuffled items. 27 | /// 28 | RepeatAllAndShuffle 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /GoogleCast/ServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using GoogleCast.Channels; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using System.Linq; 4 | using System.Reflection; 5 | 6 | namespace GoogleCast 7 | { 8 | /// 9 | /// Services registration 10 | /// 11 | public static class ServiceCollectionExtensions 12 | { 13 | /// 14 | /// Registers the services 15 | /// 16 | /// services to register 17 | /// the service descriptors collection 18 | public static IServiceCollection AddGoogleCast(this IServiceCollection services) 19 | { 20 | services.AddSingleton(); 21 | 22 | // Add channels 23 | var channelType = typeof(IChannel); 24 | foreach (var type in Assembly.GetExecutingAssembly().GetTypes().Where(t => 25 | t.IsClass && !t.IsAbstract && channelType.IsAssignableFrom(t))) 26 | { 27 | services.AddTransient(channelType, type); 28 | } 29 | 30 | return services; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /GoogleCast/Models/Media/TextTrackType.cs: -------------------------------------------------------------------------------- 1 | namespace GoogleCast.Models.Media 2 | { 3 | /// 4 | /// Possible text track types 5 | /// 6 | public enum TextTrackType 7 | { 8 | /// 9 | /// Transcription or translation of the dialogue, suitable for when the sound is available but not understood 10 | /// 11 | Subtitles, 12 | 13 | /// 14 | /// Transcription or translation of the dialogue, sound effects, relevant musical cues, and other relevant audio information, 15 | /// suitable for when the soundtrack is unavailable 16 | /// 17 | Captions, 18 | 19 | /// 20 | /// Textual descriptions of the video component of the media resource, intended for audio synthesis when 21 | /// the visual component is unavailable 22 | /// 23 | Descriptions, 24 | 25 | /// 26 | /// Chapter titles, intended to be used for navigating the media resource 27 | /// 28 | Chapters, 29 | 30 | /// 31 | /// Tracks intended for use from script 32 | /// 33 | Metadata 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /GoogleCast/Messages/Media/LoadMessage.cs: -------------------------------------------------------------------------------- 1 | using GoogleCast.Models.Media; 2 | using System.Collections.Generic; 3 | using System.Runtime.Serialization; 4 | 5 | namespace GoogleCast.Messages.Media 6 | { 7 | /// 8 | /// Message to load new content into the media player 9 | /// 10 | [DataContract] 11 | class LoadMessage : SessionMessage 12 | { 13 | /// 14 | /// Gets or sets the metadata (including contentId) of the media to load 15 | /// 16 | [DataMember(Name = "media")] 17 | public MediaInformation Media { get; set; } 18 | 19 | /// 20 | /// Gets or sets a value indicating whether the media player will begin playing the content when it is loaded or not 21 | /// 22 | [DataMember(Name = "autoplay")] 23 | public bool AutoPlay { get; set; } 24 | 25 | /// 26 | /// Gets or sets the identifiers of the tracks that should be active. 27 | /// 28 | /// if the array is not provided, the default tracks will be active 29 | [DataMember(Name = "activeTrackIds")] 30 | public IEnumerable ActiveTrackIds { get; set; } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /GoogleCast/MessageTypes.cs: -------------------------------------------------------------------------------- 1 | using GoogleCast.Messages; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Reflection; 6 | 7 | namespace GoogleCast 8 | { 9 | /// 10 | /// Dictionary of message types 11 | /// 12 | public class MessageTypes : Dictionary, IMessageTypes 13 | { 14 | /// 15 | /// Initializes a new instance of class 16 | /// 17 | public MessageTypes() 18 | { 19 | AddMessageTypes(Assembly.GetExecutingAssembly()); 20 | } 21 | 22 | /// 23 | /// Adds all the message types of a given assembly 24 | /// 25 | /// assembly 26 | public void AddMessageTypes(Assembly assembly) 27 | { 28 | var messageInterfaceType = typeof(IMessage); 29 | var receptionMessageAttributeType = typeof(ReceptionMessageAttribute); 30 | foreach (var type in assembly.GetTypes().Where(t => 31 | t.IsClass && !t.IsAbstract && t.IsDefined(receptionMessageAttributeType) && messageInterfaceType.IsAssignableFrom(t))) 32 | { 33 | Add(Message.GetMessageType(type), type); 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /GoogleCast/Channels/ConnectionChannel.cs: -------------------------------------------------------------------------------- 1 | using GoogleCast.Messages; 2 | using GoogleCast.Messages.Connection; 3 | using System.Threading.Tasks; 4 | 5 | namespace GoogleCast.Channels 6 | { 7 | /// 8 | /// Connection channel 9 | /// 10 | class ConnectionChannel : Channel, IConnectionChannel 11 | { 12 | /// 13 | /// Initializes a new instance of class 14 | /// 15 | public ConnectionChannel() : base("tp.connection") 16 | { 17 | } 18 | 19 | /// 20 | /// Connects 21 | /// 22 | /// destination identifier 23 | public async Task ConnectAsync(string destinationId) 24 | { 25 | await SendAsync(new ConnectMessage(), destinationId); 26 | } 27 | 28 | /// 29 | /// Called when a message for this channel is received 30 | /// 31 | /// message to process 32 | public async override Task OnMessageReceivedAsync(IMessage message) 33 | { 34 | if (message is CloseMessage) 35 | { 36 | await Sender.DisconnectAsync(); 37 | } 38 | await base.OnMessageReceivedAsync(message); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /GoogleCast/Receiver.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | 3 | namespace GoogleCast 4 | { 5 | /// 6 | /// GoogleCast receiver 7 | /// 8 | public class Receiver : IReceiver 9 | { 10 | /// 11 | /// Gets or sets the friendly name 12 | /// 13 | public string FriendlyName { get; set; } 14 | 15 | /// 16 | /// Gets or sets the network endpoint 17 | /// 18 | public IPEndPoint IPEndPoint { get; set; } 19 | 20 | /// 21 | /// Determines whether the specified object is equal to the current object 22 | /// 23 | /// The object to compare with the current object 24 | /// true if the specified object is equal to the current object; otherwise, false 25 | public override bool Equals(object obj) 26 | { 27 | return (obj is Receiver receiver && receiver.FriendlyName == FriendlyName && 28 | (receiver.IPEndPoint != null && receiver.IPEndPoint.Equals(IPEndPoint) || receiver.IPEndPoint == null && IPEndPoint == null)); 29 | } 30 | 31 | /// 32 | /// Serves as the default hash function 33 | /// 34 | /// A hash code for the current object 35 | public override int GetHashCode() 36 | { 37 | return IPEndPoint.GetHashCode(); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /GoogleCast/Messages/Media/EditTracksInfoMessage.cs: -------------------------------------------------------------------------------- 1 | using GoogleCast.Models.Media; 2 | using System.Collections.Generic; 3 | using System.Runtime.Serialization; 4 | 5 | namespace GoogleCast.Messages.Media 6 | { 7 | /// 8 | /// Media event EDIT_TRACKS_INFO request data 9 | /// 10 | [DataContract] 11 | class EditTracksInfoMessage : MediaSessionMessage 12 | { 13 | /// 14 | /// Gets or sets the track identifiers that should be active 15 | /// 16 | [DataMember(Name = "activeTrackIds")] 17 | public IEnumerable ActiveTrackIds { get; set; } 18 | 19 | /// 20 | /// Gets or sets a value indicating whether the text tracks should be enabled or not 21 | /// 22 | [DataMember(Name = "enableTextTracks")] 23 | public bool EnableTextTracks { get; set; } 24 | 25 | /// 26 | /// Gets or sets the language for the tracks that should be active 27 | /// 28 | [DataMember(Name = "language")] 29 | public string Language { get; set; } 30 | 31 | /// 32 | /// Gets or sets the text track style. 33 | /// If it is not provided the existing style will be used (if no style was provided in previous calls, it will be the default receiver style) 34 | /// 35 | [DataMember(Name = "textTrackStyle")] 36 | public TextTrackStyle TextTrackStyle { get; set; } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /GoogleCast/Messages/Media/QueueLoadMessage.cs: -------------------------------------------------------------------------------- 1 | using GoogleCast.Models.Media; 2 | using System.Collections.Generic; 3 | using System.Runtime.Serialization; 4 | 5 | namespace GoogleCast.Messages.Media 6 | { 7 | /// 8 | /// A request to load and optionally start playback of a new ordered list of media items 9 | /// 10 | [DataContract] 11 | class QueueLoadMessage : SessionMessage 12 | { 13 | /// 14 | /// Gets or sets the array of items to load. It is sorted (first element will be played first) 15 | /// 16 | /// must not be null or empty 17 | [DataMember(Name = "items")] 18 | public IEnumerable Items { get; set; } 19 | 20 | /// 21 | /// Gets or sets the algorithm for selection of the next item when the current item has ended 22 | /// 23 | [IgnoreDataMember] 24 | public RepeatMode RepeatMode { get; set; } 25 | 26 | [DataMember(Name = "repeatMode")] 27 | private string RepeatModeString 28 | { 29 | get { return RepeatMode.GetName(); } 30 | set { RepeatMode = EnumHelper.Parse(value); } 31 | } 32 | 33 | /// 34 | /// Gets or sets the index of the item in the items array that must be the first currentItem (the item that will be played first) 35 | /// 36 | [DataMember(Name = "startIndex")] 37 | public int StartIndex { get; set; } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("TestCSharpDll")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("TestCSharpDll")] 13 | [assembly: AssemblyCopyright("Copyright © 2010")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("c1acdbd8-6b22-4807-bba3-d0237ccd74c1")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /GoogleCast/TaskExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace GoogleCast 5 | { 6 | /// 7 | /// Extensions methods for Task class 8 | /// 9 | public static class TaskExtensions 10 | { 11 | /// 12 | /// Throws a TimeoutException if a task is not completed before a delay 13 | /// 14 | /// return type 15 | /// task 16 | /// the delay in milliseconds 17 | /// T 18 | public static async Task TimeoutAfter(this Task task, int delay) 19 | { 20 | await Task.WhenAny(task, Task.Delay(delay)); 21 | if (!task.IsCompleted) 22 | { 23 | throw new TimeoutException(); 24 | } 25 | 26 | return await task; 27 | } 28 | 29 | /// 30 | /// Throws a TimeoutException if a task is not completed before a delay 31 | /// 32 | /// task 33 | /// the delay in milliseconds 34 | /// T 35 | public static async Task TimeoutAfter(this Task task, int delay) 36 | { 37 | await Task.WhenAny(task, Task.Delay(delay)); 38 | if (!task.IsCompleted) 39 | { 40 | throw new TimeoutException(); 41 | } 42 | 43 | await task; 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /GoogleCast/DeviceLocator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net; 5 | using System.Reactive.Linq; 6 | using System.Threading.Tasks; 7 | using Zeroconf; 8 | 9 | namespace GoogleCast 10 | { 11 | /// 12 | /// Device locator 13 | /// 14 | public class DeviceLocator : IDeviceLocator 15 | { 16 | private const string PROTOCOL = "_googlecast._tcp.local."; 17 | 18 | private Receiver CreateReceiver(IZeroconfHost host) 19 | { 20 | var service = host.Services[PROTOCOL]; 21 | return new Receiver() 22 | { 23 | FriendlyName = service.Properties[0]["fn"], 24 | IPEndPoint = new IPEndPoint(IPAddress.Parse(host.IPAddress), service.Port) 25 | }; 26 | } 27 | 28 | /// 29 | /// Finds the available receivers 30 | /// 31 | /// a collection of receivers 32 | public async Task> FindReceiversAsync() 33 | { 34 | return (await ZeroconfResolver.ResolveAsync(PROTOCOL)).Select(CreateReceiver); 35 | } 36 | 37 | /// 38 | /// Finds the available receivers in continuous way 39 | /// 40 | /// a provider for notifications 41 | public IObservable FindReceiversContinuous() 42 | { 43 | return ZeroconfResolver.ResolveContinuous(PROTOCOL).Select(CreateReceiver); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /GoogleCast/Models/Media/MusicTrackMediaMetadata.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.Serialization; 2 | 3 | namespace GoogleCast.Models.Media 4 | { 5 | /// 6 | /// Music metadata 7 | /// 8 | [DataContract] 9 | public class MusicTrackMediaMetadata : GenericMediaMetadata 10 | { 11 | /// 12 | /// Initializes a new instance of class 13 | /// 14 | public MusicTrackMediaMetadata() 15 | { 16 | MetadataType = MetadataType.Music; 17 | } 18 | 19 | [DataMember(Name = "albumArtist", EmitDefaultValue = false)] 20 | public string AlbumArtist { get; set; } 21 | 22 | [DataMember(Name = "albumName", EmitDefaultValue = false)] 23 | public string AlbumName { get; set; } 24 | 25 | [DataMember(Name = "artist", EmitDefaultValue = false)] 26 | public string Artist { get; set; } 27 | 28 | [DataMember(Name = "composer", EmitDefaultValue = false)] 29 | public string Composer { get; set; } 30 | 31 | [DataMember(Name = "discNumber", EmitDefaultValue = false)] 32 | public int? DiscNumber { get; set; } 33 | 34 | [DataMember(Name = "releaseDate", EmitDefaultValue = false)] 35 | public string ReleaseDate { get; set; } 36 | 37 | [DataMember(Name = "songName", EmitDefaultValue = false)] 38 | public string SongName { get; set; } 39 | 40 | [DataMember(Name = "trackNumber", EmitDefaultValue = false)] 41 | public int? TrackNumber { get; set; } 42 | } 43 | } -------------------------------------------------------------------------------- /GoogleCast/Models/Media/GenericMediaMetadata.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.Serialization; 2 | 3 | namespace GoogleCast.Models.Media 4 | { 5 | /// 6 | /// Media metadata 7 | /// 8 | [DataContract] 9 | [KnownType(typeof(GenericMediaMetadata))] 10 | [KnownType(typeof(MovieMetadata))] 11 | [KnownType(typeof(MusicTrackMediaMetadata))] 12 | public class GenericMediaMetadata 13 | { 14 | /// 15 | /// Initializes a new instance of class 16 | /// 17 | public GenericMediaMetadata() 18 | { 19 | MetadataType = MetadataType.Default; 20 | } 21 | 22 | /// 23 | /// Gets the metadata type 24 | /// 25 | [DataMember(Name = "metadataType")] 26 | public MetadataType MetadataType { get; protected set; } 27 | 28 | /// 29 | /// Gets or sets the descriptive title of the content 30 | /// 31 | [DataMember(Name = "title")] 32 | public string Title { get; set; } 33 | 34 | /// 35 | /// Gets or sets the descriptive subtitle of the content 36 | /// 37 | [DataMember(Name = "subtitle")] 38 | public string Subtitle { get; set; } 39 | 40 | /// 41 | /// Gets or sets an array of URL(s) to an image associated with the content 42 | /// 43 | [DataMember(Name = "images")] 44 | public Image[] Images { get; set; } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /GoogleCast/Models/Receiver/Application.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Runtime.Serialization; 3 | 4 | namespace GoogleCast.Models.Receiver 5 | { 6 | /// 7 | /// Application 8 | /// 9 | [DataContract] 10 | public class Application 11 | { 12 | /// 13 | /// Gets or sets the application identifier 14 | /// 15 | [DataMember(Name = "appId")] 16 | public string AppId { get; set; } 17 | 18 | /// 19 | /// Gets or sets the display name 20 | /// 21 | [DataMember(Name = "displayName")] 22 | public string DisplayName { get; set; } 23 | 24 | /// 25 | /// Gets or sets a value indicating whether the backdrop app is running or not 26 | /// 27 | [DataMember(Name = "isIdleScreen")] 28 | public bool IsIdleScreen { get; set; } 29 | 30 | /// 31 | /// Gets or sets the namespaces 32 | /// 33 | [DataMember(Name = "namespaces")] 34 | public IEnumerable Namespaces { get; set; } 35 | 36 | /// 37 | /// Gets or sets the session identifier 38 | /// 39 | [DataMember(Name = "sessionId")] 40 | public string SessionId { get; set; } 41 | 42 | /// 43 | /// Gets or sets the status text 44 | /// 45 | [DataMember(Name = "statusText")] 46 | public string StatusText { get; set; } 47 | 48 | /// 49 | /// Gets or sets the transport identifier 50 | /// 51 | [DataMember(Name = "transportId")] 52 | public string TransportId { get; set; } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /GoogleCast/CastMessage.cs: -------------------------------------------------------------------------------- 1 | using ProtoBuf; 2 | 3 | namespace GoogleCast 4 | { 5 | /// 6 | /// Cast message 7 | /// 8 | [ProtoContract] 9 | class CastMessage 10 | { 11 | /// 12 | /// Gets or sets the protocol version 13 | /// 14 | [ProtoMember(1, IsRequired = true, Name = "protocol_version")] 15 | public ProtocolVersion ProtocolVersion { get; set; } 16 | 17 | /// 18 | /// Gets or sets the source identifier 19 | /// 20 | [ProtoMember(2, IsRequired = true, Name = "source_id")] 21 | public string SourceId { get; set; } = "sender-0"; 22 | 23 | /// 24 | /// Gets or sets the destination identifier 25 | /// 26 | [ProtoMember(3, IsRequired = true, Name = "destination_id")] 27 | public string DestinationId { get; set; } = "receiver-0"; 28 | 29 | /// 30 | /// Gets or sets the namespace 31 | /// 32 | [ProtoMember(4, IsRequired = true, Name = "namespace")] 33 | public string Namespace { get; set; } 34 | 35 | /// 36 | /// Gets or sets the payload type 37 | /// 38 | [ProtoMember(5, IsRequired = true, Name = "payload_type")] 39 | public PayloadType PayloadType { get; set; } 40 | 41 | /// 42 | /// Gets or sets the UTF-8 payload 43 | /// 44 | [ProtoMember(6, IsRequired = false, Name = "payload_utf8")] 45 | public string PayloadUtf8 { get; set; } 46 | 47 | /// 48 | /// Gets or sets the binary payload 49 | /// 50 | [ProtoMember(7, IsRequired = false, Name = "payload_binary")] 51 | public byte[] PayloadBinary { get; set; } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /app.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /GoogleCast/Channels/IReceiverChannel.cs: -------------------------------------------------------------------------------- 1 | using GoogleCast.Models.Receiver; 2 | using System.Threading.Tasks; 3 | 4 | namespace GoogleCast.Channels 5 | { 6 | /// 7 | /// Interface for the receiver channel 8 | /// 9 | public interface IReceiverChannel : IStatusChannel 10 | { 11 | /// 12 | /// Retrieves the status 13 | /// 14 | /// the status 15 | Task GetStatusAsync(); 16 | 17 | /// 18 | /// Launches an application 19 | /// 20 | /// application identifier 21 | /// receiver status 22 | Task LaunchAsync(string applicationId); 23 | 24 | /// 25 | /// Checks the connection is well established 26 | /// 27 | /// namespace 28 | /// an application object 29 | Task EnsureConnectionAsync(string ns); 30 | 31 | /// 32 | /// Sets the volume 33 | /// 34 | /// volume level (0.0 to 1.0) 35 | /// receiver status 36 | Task SetVolumeAsync(float level); 37 | 38 | /// 39 | /// Sets a value indicating whether the audio should be muted 40 | /// 41 | /// true if audio should be muted; otherwise, false 42 | /// receiver status 43 | Task SetIsMutedAsync(bool isMuted); 44 | 45 | /// 46 | /// Stops the current applications 47 | /// 48 | /// applications to stop 49 | /// ReceiverStatus 50 | Task StopAsync(params Application[] applications); 51 | } 52 | } -------------------------------------------------------------------------------- /packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Settings.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Text; 4 | using System.Threading; 5 | using System.Windows.Forms; 6 | using System.Xml; 7 | 8 | namespace MusicBeePlugin 9 | { 10 | public partial class Settings : Form 11 | { 12 | private static string SettingsPath { get; set; } 13 | 14 | public Settings(string @path) 15 | { 16 | SettingsPath = path; 17 | InitializeComponent(); 18 | ReadSettings(); 19 | 20 | } 21 | 22 | private void folderBrowserDialog1_HelpRequest(object sender, EventArgs e) 23 | { 24 | 25 | } 26 | 27 | 28 | 29 | public void CreateSettings() 30 | { 31 | using (var writer = new XmlTextWriter(SettingsPath + @"\MB_Chromecast_Settings.xml", Encoding.UTF8)) 32 | { 33 | writer.Formatting = Formatting.Indented; 34 | //Write the root element 35 | writer.WriteStartElement("settings"); 36 | 37 | //Write sub-elements 38 | writer.WriteElementString("server_port", ((int)serverPortSelect.Value).ToString()); 39 | 40 | // end the root element 41 | writer.WriteEndElement(); 42 | } 43 | 44 | } 45 | 46 | private void ReadSettings() 47 | { 48 | if (File.Exists(SettingsPath + @"\MB_Chromecast_Settings.xml")) 49 | { 50 | XmlDocument doc = new XmlDocument(); 51 | doc.Load(SettingsPath + @"\MB_Chromecast_Settings.xml"); 52 | var temp = doc.GetElementsByTagName("server_port")[0].InnerText; 53 | if (!string.IsNullOrEmpty(temp)) 54 | { 55 | serverPortSelect.Value = Convert.ToDecimal(temp); 56 | } 57 | } 58 | } 59 | 60 | private void closeText_Click(object sender, EventArgs e) 61 | { 62 | this.Close(); 63 | } 64 | 65 | private void saveText_Click(object sender, EventArgs e) 66 | { 67 | CreateSettings(); 68 | this.Close(); 69 | } 70 | 71 | 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /GoogleCast/Models/Media/QueueItem.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Runtime.Serialization; 3 | 4 | namespace GoogleCast.Models.Media 5 | { 6 | /// 7 | /// Queue item information 8 | /// 9 | [DataContract] 10 | public class QueueItem 11 | { 12 | /// 13 | /// Gets or sets the track identifiers that are active 14 | /// 15 | [DataMember(Name = "activeTrackIds", EmitDefaultValue = false)] 16 | public IEnumerable ActiveTrackIds { get; set; } 17 | 18 | /// 19 | /// Gets or sets a value indicating whether the media will automatically play 20 | /// 21 | [DataMember(Name = "autoplay")] 22 | public bool Autoplay { get; set; } = true; 23 | 24 | /// 25 | /// Gets or sets the unique identifier of the item in the queue 26 | /// 27 | /// the attribute is optional because for LOAD or INSERT should not be provided 28 | /// (as it will be assigned by the receiver when an item is first created/inserted). 29 | [DataMember(Name = "itemId", EmitDefaultValue = false)] 30 | public int? ItemId { get; set; } 31 | 32 | /// 33 | /// Gets or sets the metadata (including contentId) of the playlist element 34 | /// 35 | [DataMember(Name = "media")] 36 | public MediaInformation Media { get; set; } 37 | 38 | /// 39 | /// Gets or sets the seconds from the beginning of the media to start playback 40 | /// 41 | [DataMember(Name = "startTime", EmitDefaultValue = false)] 42 | public int? StartTime { get; set; } 43 | 44 | /// 45 | /// Gets or sets the seconds from the beginning of the media to start playback 46 | /// 47 | [DataMember(Name = "preloadTime", EmitDefaultValue = false)] 48 | public int? PreloadTime { get; set; } 49 | 50 | /// 51 | /// Gets or sets the ordered number of the queue item 52 | /// 53 | [DataMember(Name = "orderId", EmitDefaultValue = false)] 54 | public int? OrderId { get; set; } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /GoogleCast/ColorHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Drawing; 3 | 4 | namespace GoogleCast 5 | { 6 | /// 7 | /// Helper for class 8 | /// 9 | public static class ColorHelper 10 | { 11 | /// 12 | /// Converts a string to a 13 | /// 14 | /// color string to convert 15 | /// the color object 16 | public static Color FromHexString(string color) 17 | { 18 | return Color.FromArgb( 19 | Convert.ToInt32(color.Substring(7, 2), 16), 20 | Convert.ToInt32(color.Substring(1, 2), 16), 21 | Convert.ToInt32(color.Substring(3, 2), 16), 22 | Convert.ToInt32(color.Substring(5, 2), 16)); 23 | } 24 | 25 | /// 26 | /// Converts a string to a nullable 27 | /// 28 | /// color string to convert 29 | /// the nullable color object 30 | public static Color? FromNullableHexString(string color) 31 | { 32 | if (string.IsNullOrEmpty(color)) 33 | { 34 | return null; 35 | } 36 | 37 | return FromHexString(color); 38 | } 39 | 40 | /// 41 | /// Converts a color to an hexadecimal string (#RRGGBBAA) 42 | /// 43 | /// color to convert 44 | /// the hexadecimal string 45 | public static string ToHexString(this Color color) 46 | { 47 | return $"#{color.R.ToString("X2", null)}{color.G.ToString("X2", null)}{color.B.ToString("X2", null)}{color.A.ToString("X2", null)}"; 48 | } 49 | 50 | /// 51 | /// Converts a nullable color to an hexadecimal string (#RRGGBBAA) 52 | /// 53 | /// nullable color to convert 54 | /// the hexadecimal string 55 | public static string ToHexString(this Color? color) 56 | { 57 | return color == null ? null : ToHexString((Color)color); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /GoogleCast/Channels/StatusChannel.cs: -------------------------------------------------------------------------------- 1 | using GoogleCast.Messages; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Threading.Tasks; 5 | 6 | namespace GoogleCast.Channels 7 | { 8 | /// 9 | /// Base class for status channels 10 | /// 11 | /// status type 12 | /// status message type 13 | public abstract class StatusChannel : Channel, IStatusChannel 14 | where TStatusMessage : IStatusMessage 15 | { 16 | /// 17 | /// Raised when the status has changed 18 | /// 19 | public event EventHandler StatusChanged; 20 | 21 | /// 22 | /// Initialization 23 | /// 24 | /// namespace 25 | public StatusChannel(string ns) : base(ns) 26 | { 27 | } 28 | 29 | private TStatus _status; 30 | /// 31 | /// Gets or sets the status 32 | /// 33 | public TStatus Status 34 | { 35 | get { return _status; } 36 | protected set 37 | { 38 | if (!EqualityComparer.Default.Equals(_status, value)) 39 | { 40 | _status = value; 41 | OnStatusChanged(); 42 | } 43 | } 44 | } 45 | 46 | /// 47 | /// Called when a message for this channel is received 48 | /// 49 | /// message to process 50 | public override Task OnMessageReceivedAsync(IMessage message) 51 | { 52 | switch (message) 53 | { 54 | case TStatusMessage statusMessage: 55 | Status = statusMessage.Status; 56 | break; 57 | } 58 | 59 | return base.OnMessageReceivedAsync(message); 60 | } 61 | 62 | /// 63 | /// Raises the StatusChanged event 64 | /// 65 | protected virtual void OnStatusChanged() 66 | { 67 | StatusChanged?.Invoke(this, EventArgs.Empty); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /GoogleCast/Channels/Channel.cs: -------------------------------------------------------------------------------- 1 | using GoogleCast.Messages; 2 | using System.Threading.Tasks; 3 | 4 | namespace GoogleCast.Channels 5 | { 6 | /// 7 | /// Channel base class 8 | /// 9 | public abstract class Channel : IChannel 10 | { 11 | private const string BASE_NAMESPACE = "urn:x-cast:com.google.cast"; 12 | 13 | /// 14 | /// Initialization 15 | /// 16 | protected Channel() 17 | { 18 | } 19 | 20 | /// 21 | /// Initialization 22 | /// 23 | /// namespace 24 | protected Channel(string ns) 25 | { 26 | Namespace = $"{BASE_NAMESPACE}.{ns}"; 27 | } 28 | 29 | /// 30 | /// Gets or sets the sender 31 | /// 32 | public virtual ISender Sender { get; set; } 33 | 34 | /// 35 | /// Gets the full namespace 36 | /// 37 | public string Namespace { get; protected set; } 38 | 39 | /// 40 | /// Sends a message 41 | /// 42 | /// message to send 43 | /// destination identifier 44 | protected async Task SendAsync(IMessage message, string destinationId = DefaultIdentifiers.DESTINATION_ID) 45 | { 46 | await Sender.SendAsync(Namespace, message, destinationId); 47 | } 48 | 49 | /// 50 | /// Sends a message and waits the result 51 | /// 52 | /// response type 53 | /// message to send 54 | /// destination identifier 55 | /// the result 56 | protected async Task SendAsync(IMessageWithId message, string destinationId = DefaultIdentifiers.DESTINATION_ID) where TResponse : IMessageWithId 57 | { 58 | return await Sender.SendAsync(Namespace, message, destinationId); 59 | } 60 | 61 | /// 62 | /// Called when a message for this channel is received 63 | /// 64 | /// message to process 65 | public virtual Task OnMessageReceivedAsync(IMessage message) 66 | { 67 | return Task.CompletedTask; 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /GoogleCast/Models/Media/Track.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.Serialization; 2 | 3 | namespace GoogleCast.Models.Media 4 | { 5 | /// 6 | /// Track metadata information 7 | /// 8 | [DataContract] 9 | public class Track 10 | { 11 | /// 12 | /// Gets or sets the unique identifier of the track 13 | /// 14 | [DataMember(Name = "trackId")] 15 | public int TrackId { get; set; } 16 | 17 | /// 18 | /// Gets or sets the type of track 19 | /// 20 | [IgnoreDataMember] 21 | public TrackType Type { get; set; } = TrackType.Text; 22 | 23 | [DataMember(Name = "type")] 24 | private string TypeString 25 | { 26 | get { return Type.GetName(); } 27 | set { Type = EnumHelper.Parse(value); } 28 | } 29 | 30 | /// 31 | /// Gets or sets the MIME type of the track content 32 | /// 33 | [DataMember(Name = "trackContentType")] 34 | public string TrackContentType { get; set; } = "text/vtt"; 35 | 36 | /// 37 | /// Gets or sets the identifier of the track’s content 38 | /// 39 | /// it can be the url of the track or any other identifier that allows the receiver to find the content 40 | /// (when the track is not inband or included in the manifest) 41 | [DataMember(Name = "trackContentId")] 42 | public string TrackContentId { get; set; } 43 | 44 | /// 45 | /// Gets or sets the type of text track 46 | /// 47 | [IgnoreDataMember] 48 | public TextTrackType SubType { get; set; } 49 | 50 | [DataMember(Name = "subType")] 51 | private string SubTypeString 52 | { 53 | get { return SubType.GetName(); } 54 | set { SubType = EnumHelper.Parse(value); } 55 | } 56 | 57 | /// 58 | /// Gets or sets a descriptive, human readable name for the track 59 | /// 60 | [DataMember(Name = "name")] 61 | public string Name { get; set; } 62 | 63 | /// 64 | /// Gets or sets the language tag as per RFC 5646 65 | /// 66 | /// mandatory when the subtype is Subtitles 67 | [DataMember(Name = "language")] 68 | public string Language { get; set; } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /GoogleCast/Models/Media/MediaInformation.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Runtime.Serialization; 3 | 4 | namespace GoogleCast.Models.Media 5 | { 6 | /// 7 | /// Describes a media stream 8 | /// 9 | [DataContract] 10 | public class MediaInformation 11 | { 12 | /// 13 | /// Gets or sets the service-specific identifier of the content currently loaded by the media player 14 | /// 15 | [DataMember(Name = "contentId")] 16 | public string ContentId { get; set; } 17 | 18 | /// 19 | /// Gets or sets the type of media artifact 20 | /// 21 | [IgnoreDataMember] 22 | public StreamType StreamType { get; set; } = StreamType.Buffered; 23 | 24 | [DataMember(Name = "streamType")] 25 | private string StreamTypeString 26 | { 27 | get { return StreamType.GetName(); } 28 | set { StreamType = EnumHelper.Parse(value); } 29 | } 30 | 31 | /// 32 | /// Gets or sets the MIME content type of the media being played 33 | /// 34 | [DataMember(Name = "contentType", EmitDefaultValue = false)] 35 | public string ContentType { get; set; } 36 | 37 | /// 38 | /// Gets or sets the media metadata object 39 | /// 40 | [DataMember(Name = "metadata", EmitDefaultValue = false)] 41 | public GenericMediaMetadata Metadata { get; set; } 42 | 43 | /// 44 | /// Gets or sets the duration of the currently playing stream in seconds 45 | /// 46 | [DataMember(Name = "duration", EmitDefaultValue = false)] 47 | public double? Duration { get; set; } 48 | 49 | /// 50 | /// Gets or sets the custom data 51 | /// 52 | [DataMember(Name = "customData", EmitDefaultValue = false)] 53 | public IDictionary CustomData { get; set; } 54 | 55 | /// 56 | /// Gets or sets the tracks 57 | /// 58 | [DataMember(Name = "tracks", EmitDefaultValue = false)] 59 | public IEnumerable Tracks { get; set; } 60 | 61 | /// 62 | /// Gets or sets the style of text track 63 | /// 64 | [DataMember(Name = "textTrackStyle", EmitDefaultValue = false)] 65 | public TextTrackStyle TextTrackStyle { get; set; } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /GoogleCast/GoogleCast.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %24/G-Matt/GoogleCast/GoogleCast 5 | {4CA58AB2-18FA-4F8D-95D4-32DDF27D184C} 6 | https://mattmckenzy.visualstudio.com 7 | . 8 | 9 | 10 | 11 | netstandard2.0 12 | True 13 | False 14 | key.pfx 15 | Stéphane Mitermite 16 | Stéphane Mitermite 17 | Implementation of the Google Cast protocol (.NET Standard 2.0 library). 18 | Copyright © 2018 Stéphane Mitermite 19 | https://github.com/kakone/GoogleCast/blob/master/LICENSE 20 | https://github.com/kakone/GoogleCast 21 | google cast googlecast chromecast 22 | True 23 | 1.6.5.0 24 | 1.6.5.0 25 | 1.6.5 26 | https://github.com/kakone/GoogleCast/raw/master/GoogleCast.png 27 | 28 | 29 | 30 | 4 31 | bin\Debug\netstandard2.0\GoogleCast.xml 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /GoogleCast/StringExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | 4 | namespace GoogleCast 5 | { 6 | /// 7 | /// Extensions methods for 8 | /// 9 | public static class StringExtensions 10 | { 11 | /// 12 | /// Converts a camel case string to underscore notation 13 | /// 14 | /// string to convert 15 | /// the converted string to underscore notation 16 | public static string ToUnderscoreUpperInvariant(this string str) 17 | { 18 | if (string.IsNullOrEmpty(str)) 19 | { 20 | return str; 21 | } 22 | 23 | var stringBuilder = new StringBuilder(); 24 | var first = true; 25 | foreach (var c in str) 26 | { 27 | if (first) 28 | { 29 | first = false; 30 | } 31 | else 32 | { 33 | if (Char.IsUpper(c)) 34 | { 35 | stringBuilder.AppendFormat("_{0}", c); 36 | continue; 37 | } 38 | } 39 | stringBuilder.Append(Char.ToUpperInvariant(c)); 40 | } 41 | return stringBuilder.ToString(); 42 | } 43 | 44 | /// 45 | /// Converts a string to camel case notation 46 | /// 47 | /// string to convert 48 | /// camel case notation 49 | public static string ToCamelCase(this string str) 50 | { 51 | if (string.IsNullOrEmpty(str)) 52 | { 53 | return str; 54 | } 55 | 56 | var stringBuilder = new StringBuilder(); 57 | var underscore = true; 58 | foreach (var c in str) 59 | { 60 | if (underscore) 61 | { 62 | underscore = false; 63 | stringBuilder.Append(Char.ToUpperInvariant(c)); 64 | } 65 | else 66 | { 67 | if (c == '_') 68 | { 69 | underscore = true; 70 | } 71 | else 72 | { 73 | stringBuilder.Append(Char.ToLowerInvariant(c)); 74 | } 75 | } 76 | }; 77 | return stringBuilder.ToString(); 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /GoogleCast/Models/Media/MediaStatus.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Runtime.Serialization; 3 | 4 | namespace GoogleCast.Models.Media 5 | { 6 | /// 7 | /// Media status 8 | /// 9 | [DataContract] 10 | public class MediaStatus 11 | { 12 | /// 13 | /// Gets or sets the media session identifier 14 | /// 15 | [DataMember(Name = "mediaSessionId")] 16 | public long MediaSessionId { get; set; } 17 | 18 | /// 19 | /// Gets or sets the playback rate 20 | /// 21 | [DataMember(Name = "playbackRate")] 22 | public double PlaybackRate { get; set; } 23 | 24 | /// 25 | /// Gets or sets the player state 26 | /// 27 | [DataMember(Name = "playerState")] 28 | public string PlayerState { get; set; } 29 | 30 | /// 31 | /// Gets or sets the current time 32 | /// 33 | [DataMember(Name = "currentTime")] 34 | public double CurrentTime { get; set; } 35 | 36 | /// 37 | /// Gets or sets the supported media commands 38 | /// 39 | [DataMember(Name = "supportedMediaCommands")] 40 | public int SupportedMediaCommands { get; set; } 41 | 42 | /// 43 | /// Gets or sets the volume 44 | /// 45 | [DataMember(Name = "volume")] 46 | public Volume Volume { get; set; } 47 | 48 | /// 49 | /// Gets or sets the idle reason 50 | /// 51 | [DataMember(Name = "idleReason")] 52 | public string IdleReason { get; set; } 53 | 54 | /// 55 | /// Gets or sets the media 56 | /// 57 | [DataMember(Name = "media")] 58 | public MediaInformation Media { get; set; } 59 | 60 | /// 61 | /// Gets or sets the current item identifier 62 | /// 63 | [DataMember(Name = "currentItemId")] 64 | public int CurrentItemId { get; set; } 65 | 66 | /// 67 | /// Gets or sets the extended status 68 | /// 69 | [DataMember(Name = "extendedStatus")] 70 | public MediaStatus ExtendedStatus { get; set; } 71 | 72 | /// 73 | /// Gets or sets the repeat mode 74 | /// 75 | [DataMember(Name = "repeatMode")] 76 | public string RepeatMode { get; set; } 77 | 78 | /// 79 | /// Gets or sets the queue items 80 | /// 81 | [DataMember(Name = "items")] 82 | public IEnumerable QueueItems { get; set; } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /GoogleCast/EnumHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace GoogleCast 4 | { 5 | /// 6 | /// Helper for 7 | /// 8 | public static class EnumHelper 9 | { 10 | /// 11 | /// Converts the string representation of the name of an enumerated constant to an equivalent enumerated object 12 | /// 13 | /// an enumeration type 14 | /// a string containing the name to convert 15 | /// an object of type T whose value is represented by enumString 16 | public static T Parse(string enumString) where T : struct, IConvertible 17 | { 18 | return (T)Enum.Parse(typeof(T), enumString.ToCamelCase(), false); 19 | } 20 | 21 | /// 22 | /// Converts the nullable string representation of the name of an enumerated constant to an equivalent enumerated object 23 | /// 24 | /// an enumeration type 25 | /// a nullable string containing the name to convert 26 | /// an object of type T whose value is represented by enumString 27 | public static T? ParseNullable(string enumString) where T : struct, IConvertible 28 | { 29 | return string.IsNullOrEmpty(enumString) ? (T?)null : Parse(enumString); 30 | } 31 | 32 | /// 33 | /// Retrieves the name of the constant in the specified enumeration that has the specified value 34 | /// 35 | /// an enumeration type 36 | /// the value of a particular enumerated constant in terms of its underlying type 37 | /// a string containing the name of the enumerated constant in enumType whose value is value; or null if no such constant is found 38 | public static string GetName(this T value) where T : struct, IConvertible 39 | { 40 | return Enum.GetName(typeof(T), value).ToUnderscoreUpperInvariant(); 41 | } 42 | 43 | /// 44 | /// Retrieves the name of the constant in the specified enumeration that has the specified value 45 | /// 46 | /// an enumeration type 47 | /// the value of a particular enumerated constant in terms of its underlying type 48 | /// a string containing the name of the enumerated constant in enumType whose value is value; or null if no such constant is found 49 | public static string GetName(this T? value) where T : struct, IConvertible 50 | { 51 | return value == null ? null : GetName((T)value); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace MusicBeePlugin.Properties { 12 | using System; 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal Resources() { 33 | } 34 | 35 | /// 36 | /// Returns the cached ResourceManager instance used by this class. 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("MusicBeePlugin.Properties.Resources", typeof(Resources).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Overrides the current thread's CurrentUICulture property for all 51 | /// resource lookups using this strongly typed resource class. 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | internal static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /GoogleCast/JsonSerializer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Runtime.Serialization.Json; 4 | using System.Text; 5 | 6 | namespace GoogleCast 7 | { 8 | /// 9 | /// Json serializer 10 | /// 11 | public static class JsonSerializer 12 | { 13 | /// 14 | /// Serializes an object to a JSON UTF-8 string 15 | /// 16 | /// object to serialize 17 | /// a string corresponding to the serialized object 18 | public static string SerializeToUTF8String(object obj) 19 | { 20 | return Encoding.UTF8.GetString(Serialize(obj)); 21 | } 22 | 23 | /// 24 | /// Serializes an object to JSON 25 | /// 26 | /// object to serialize 27 | /// a JSON byte array 28 | public static byte[] Serialize(object obj) 29 | { 30 | using (var ms = new MemoryStream()) 31 | { 32 | new DataContractJsonSerializer(obj.GetType()).WriteObject(ms, obj); 33 | return ms.ToArray(); 34 | } 35 | } 36 | 37 | /// 38 | /// Deserializes an JSON UTF-8 string to an object 39 | /// 40 | /// object type 41 | /// JSON UTF-8 string 42 | /// the corresponding object 43 | public static T Deserialize(string utf8String) where T : class 44 | { 45 | return (T)Deserialize(typeof(T), utf8String); 46 | } 47 | 48 | /// 49 | /// Deserializes an JSON byte array to an object 50 | /// 51 | /// object type 52 | /// JSON byte array 53 | /// the corresponding object 54 | public static T Deserialize(byte[] str) where T : class 55 | { 56 | return (T)Deserialize(typeof(T), str); 57 | } 58 | 59 | /// 60 | /// Deserializes an JSON UTF-8 string to an object 61 | /// 62 | /// object type 63 | /// JSON UTF-8 string 64 | /// the corresponding object 65 | public static object Deserialize(Type type, string utf8String) 66 | { 67 | return Deserialize(type, String.IsNullOrWhiteSpace(utf8String) ? null : Encoding.UTF8.GetBytes(utf8String)); 68 | } 69 | 70 | /// 71 | /// Deserializes an JSON byte array to an object 72 | /// 73 | /// object type 74 | /// JSON byte array 75 | /// the corresponding object 76 | public static object Deserialize(Type type, byte[] str) 77 | { 78 | if (str != null) 79 | { 80 | using (var ms = new MemoryStream(str)) 81 | { 82 | return new DataContractJsonSerializer(type).ReadObject(ms); 83 | } 84 | } 85 | 86 | return null; 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /MBCCRules/app.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 52 | 59 | 60 | 61 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /GoogleCast/ISender.cs: -------------------------------------------------------------------------------- 1 | using GoogleCast.Channels; 2 | using GoogleCast.Messages; 3 | using GoogleCast.Models.Receiver; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Threading.Tasks; 7 | 8 | namespace GoogleCast 9 | { 10 | /// 11 | /// Interface for the GoogleCast sender 12 | /// 13 | public interface ISender 14 | { 15 | /// 16 | /// Raised when the sender is disconnected 17 | /// 18 | event EventHandler Disconnected; 19 | 20 | /// 21 | /// Gets a channel 22 | /// 23 | /// channel type 24 | /// a channel 25 | TChannel GetChannel() where TChannel : IChannel; 26 | 27 | /// 28 | /// Connects to a receiver 29 | /// 30 | /// receiver 31 | Task ConnectAsync(IReceiver receiver); 32 | 33 | /// 34 | /// Disconnects 35 | /// 36 | Task DisconnectAsync(); 37 | 38 | /// 39 | /// Launches an application 40 | /// 41 | /// application channel type 42 | /// receiver status 43 | Task LaunchAsync() where TAppChannel : IApplicationChannel; 44 | 45 | /// 46 | /// Launches an application 47 | /// 48 | /// application channel 49 | /// receiver status 50 | Task LaunchAsync(IApplicationChannel applicationChannel); 51 | 52 | /// 53 | /// Launches an application 54 | /// 55 | /// application identifier 56 | /// receiver status 57 | Task LaunchAsync(string applicationId); 58 | 59 | /// 60 | /// Sends a message 61 | /// 62 | /// namespace 63 | /// message to send 64 | /// destination identifier 65 | Task SendAsync(string ns, IMessage message, string destinationId); 66 | 67 | /// 68 | /// Sends a message 69 | /// 70 | /// response type 71 | /// namespace 72 | /// message to send 73 | /// destination identifier 74 | /// the result 75 | Task SendAsync(string ns, IMessageWithId message, string destinationId) 76 | where TResponse : IMessageWithId; 77 | 78 | /// 79 | /// Gets the differents statuses 80 | /// 81 | /// a dictionnary of namespace/status 82 | IDictionary GetStatuses(); 83 | 84 | /// 85 | /// Restore the differents statuses 86 | /// 87 | /// statuses to restore 88 | void RestoreStatuses(IDictionary statuses); 89 | } 90 | } -------------------------------------------------------------------------------- /MBCCRules/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using NetFwTypeLib; 3 | using System.Security.Permissions; 4 | using System.Linq; 5 | 6 | namespace MBCCRules 7 | { 8 | class Program 9 | { 10 | static void Main(string[] args) 11 | { 12 | 13 | if (args.Length != 1) 14 | { 15 | System.Console.WriteLine("Please enter a port number."); 16 | System.Console.WriteLine("usage: .exe [port] "); 17 | Console.WriteLine("Press any key to exit"); 18 | Console.ReadLine(); 19 | return; 20 | } 21 | 22 | string port = args[0]; 23 | 24 | 25 | Console.WriteLine("Creating Firewall Rule"); 26 | if (CreateFirewallRule(port)) 27 | { 28 | Console.WriteLine("Created Firewall Rule (Name: Musicbee Chromecast)"); 29 | } 30 | else 31 | { 32 | Console.WriteLine("Error creating firewall rule"); 33 | } 34 | 35 | Console.WriteLine("Creating Access Rule"); 36 | 37 | if (CreateAccessRule(port)) 38 | { 39 | Console.WriteLine("Created access rule"); 40 | } 41 | else 42 | { 43 | Console.WriteLine("Error creating access rule"); 44 | } 45 | 46 | Console.WriteLine("Finished. Press any key to exit"); 47 | Console.ReadLine(); 48 | } 49 | 50 | private static bool CreateFirewallRule(string port) 51 | { 52 | try 53 | { 54 | INetFwPolicy2 firewallPolicy = (INetFwPolicy2)Activator.CreateInstance(Type.GetTypeFromProgID("HNetCfg.FwPolicy2")); 55 | 56 | INetFwRule firewallRule = firewallPolicy.Rules.OfType().Where(x => x.Name == "Musicbee Chromecast").FirstOrDefault(); 57 | 58 | if (firewallRule == null) 59 | { 60 | firewallRule = (INetFwRule)Activator.CreateInstance(Type.GetTypeFromProgID("HNetCfg.FWRule")); 61 | firewallRule.Action = NET_FW_ACTION_.NET_FW_ACTION_ALLOW; 62 | firewallRule.Profiles = (int)NET_FW_PROFILE_TYPE2_.NET_FW_PROFILE2_PRIVATE; 63 | firewallRule.Description = "Allow access within local network from port " + port; 64 | firewallRule.Direction = NET_FW_RULE_DIRECTION_.NET_FW_RULE_DIR_IN; 65 | firewallRule.Enabled = true; 66 | firewallRule.InterfaceTypes = "All"; 67 | firewallRule.Protocol = (int)NET_FW_IP_PROTOCOL_.NET_FW_IP_PROTOCOL_TCP; 68 | firewallRule.LocalPorts = port; 69 | firewallRule.Name = "Musicbee Chromecast"; 70 | firewallPolicy.Rules.Add(firewallRule); 71 | } 72 | return true; 73 | } 74 | 75 | catch (Exception) 76 | { 77 | return false; 78 | } 79 | } 80 | 81 | private static bool CreateAccessRule(string port) 82 | { 83 | try 84 | { 85 | System.Diagnostics.Process.Start("netsh.exe", "http add urlacl url=http://*:" + port + "/ user=Everyone"); 86 | 87 | return true; 88 | } 89 | catch 90 | { 91 | return false; 92 | } 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /WebServer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Net; 4 | using System.Net.Sockets; 5 | using System.Threading.Tasks; 6 | using System.Windows; 7 | using Microsoft.Owin; 8 | using Microsoft.Owin.FileSystems; 9 | using Microsoft.Owin.Host.HttpListener; 10 | using Microsoft.Owin.Hosting; 11 | using Microsoft.Owin.StaticFiles; 12 | using Microsoft.Owin.StaticFiles.ContentTypes; 13 | using Owin; 14 | 15 | 16 | namespace MusicBeePlugin 17 | { 18 | public class WebServer : IDisposable 19 | { 20 | public IDisposable MediaWebServer { get; set; } = null; 21 | bool disposed; 22 | 23 | private static readonly IPEndPoint DefaultLoopbackEndpoint = new IPEndPoint(IPAddress.Loopback, port: 0); 24 | 25 | public int? MEDIA_PORT { get; set; } 26 | 27 | protected virtual void Dispose(bool disposing) 28 | { 29 | if (!disposed) 30 | { 31 | if (disposing) 32 | { 33 | if (MediaWebServer != null) 34 | { 35 | MediaWebServer.Dispose(); 36 | } 37 | Debug.WriteLine("closing webserver"); 38 | } 39 | } 40 | 41 | //dispose unmanaged resources 42 | disposed = true; 43 | } 44 | 45 | public void Dispose() 46 | { 47 | Dispose(true); 48 | GC.SuppressFinalize(this); 49 | } 50 | 51 | public WebServer(int? port_number, string @imageDirectory = null) 52 | { 53 | 54 | try 55 | { 56 | MEDIA_PORT = port_number ?? throw new Exception("Port Number Undefined"); 57 | 58 | var mediaURL = "http://*:" + MEDIA_PORT; 59 | string path = @System.IO.Path.GetTempPath() + @"\\MusicBeeChromecast"; 60 | System.IO.Directory.CreateDirectory(path); 61 | var mediaFileSystem = new PhysicalFileSystem(path); 62 | 63 | var mediaServerOptions = new FileServerOptions 64 | { 65 | EnableDirectoryBrowsing = true, 66 | FileSystem = mediaFileSystem 67 | }; 68 | mediaServerOptions.StaticFileOptions.ContentTypeProvider = new CustomContentTypeProvider(); 69 | 70 | MediaWebServer = WebApp.Start(mediaURL, builder => builder.UseFileServer(mediaServerOptions)); 71 | //Debug.WriteLine(mediaURL); 72 | 73 | Debug.WriteLine("Listening at " + mediaURL); 74 | 75 | } 76 | catch (Exception e) 77 | { 78 | MessageBox.Show(e.Message); 79 | 80 | //Change this after 81 | throw new Exception("Webserver exception"); 82 | } 83 | 84 | } 85 | 86 | 87 | public class CustomContentTypeProvider : FileExtensionContentTypeProvider 88 | { 89 | public CustomContentTypeProvider() 90 | { 91 | Mappings.Add(".flac", "audio/flac"); 92 | //The webserver can understand to treat the .tmp image files music bee produces as image files, 93 | //however the chromecast needs a proper image format sent to it. 94 | Mappings.Add(".tmp", "image/png"); 95 | } 96 | } 97 | 98 | 99 | public void Stop() 100 | { 101 | if (MediaWebServer != null) 102 | { 103 | Dispose(); 104 | } 105 | else 106 | { 107 | throw new NullReferenceException("Webserver is null"); 108 | } 109 | } 110 | 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /ChromecastPanel.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace MusicBeePlugin 2 | { 3 | partial class ChromecastPanel 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Windows Form Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | this.closeText = new System.Windows.Forms.Label(); 32 | this.flowLayoutPanel1 = new System.Windows.Forms.FlowLayoutPanel(); 33 | this.devicesText = new System.Windows.Forms.Label(); 34 | this.SuspendLayout(); 35 | // 36 | // closeText 37 | // 38 | this.closeText.AutoSize = true; 39 | this.closeText.Location = new System.Drawing.Point(159, 279); 40 | this.closeText.Name = "closeText"; 41 | this.closeText.Size = new System.Drawing.Size(33, 13); 42 | this.closeText.TabIndex = 0; 43 | this.closeText.Text = "Close"; 44 | this.closeText.Click += new System.EventHandler(this.label1_Click); 45 | // 46 | // flowLayoutPanel1 47 | // 48 | this.flowLayoutPanel1.Location = new System.Drawing.Point(1, 36); 49 | this.flowLayoutPanel1.Name = "flowLayoutPanel1"; 50 | this.flowLayoutPanel1.Size = new System.Drawing.Size(204, 240); 51 | this.flowLayoutPanel1.TabIndex = 1; 52 | // 53 | // devicesText 54 | // 55 | this.devicesText.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); 56 | this.devicesText.Location = new System.Drawing.Point(12, 9); 57 | this.devicesText.Name = "devicesText"; 58 | this.devicesText.Size = new System.Drawing.Size(77, 24); 59 | this.devicesText.TabIndex = 3; 60 | this.devicesText.Text = "Devices"; 61 | this.devicesText.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; 62 | // 63 | // ChromecastPanel 64 | // 65 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 66 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 67 | this.ClientSize = new System.Drawing.Size(206, 301); 68 | this.Controls.Add(this.devicesText); 69 | this.Controls.Add(this.flowLayoutPanel1); 70 | this.Controls.Add(this.closeText); 71 | this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None; 72 | this.Name = "ChromecastPanel"; 73 | this.Text = "ChromecastPanel"; 74 | this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.ChromecastSelection_FormClosing); 75 | this.Load += new System.EventHandler(this.ChromecastPanel_Load); 76 | this.ResumeLayout(false); 77 | this.PerformLayout(); 78 | 79 | } 80 | 81 | #endregion 82 | 83 | private System.Windows.Forms.Label closeText; 84 | private System.Windows.Forms.FlowLayoutPanel flowLayoutPanel1; 85 | private System.Windows.Forms.Label devicesText; 86 | } 87 | } -------------------------------------------------------------------------------- /CSharp.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.30011.22 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CSharpDll", "CSharpDll.csproj", "{F5D46BA1-6F21-40EF-9695-46105CCACD08}" 7 | EndProject 8 | Project("{911E67C6-3D85-4FCE-B560-20A9C3E3FF48}") = "MusicBee", "E:\Users\Troy\Desktop\MusicbeePortable\MusicBee\MusicBee.exe", "{359FF0BF-12BB-4DF4-B550-D68FFA9D1E9C}" 9 | ProjectSection(DebuggerProjectSystem) = preProject 10 | PortSupplier = 00000000-0000-0000-0000-000000000000 11 | Executable = E:\Users\Troy\Desktop\MusicbeePortable\MusicBee\MusicBee.exe 12 | RemoteMachine = TROYDESKTOP 13 | StartingDirectory = E:\Users\Troy\Desktop\MusicbeePortable\MusicBee 14 | Arguments = runas.exe /savecred /user:administrator 15 | Environment = Default 16 | LaunchingEngine = 00000000-0000-0000-0000-000000000000 17 | UseLegacyDebugEngines = No 18 | LaunchSQLEngine = No 19 | AttachLaunchAction = No 20 | EndProjectSection 21 | EndProject 22 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MBCCRules", "MBCCRules\MBCCRules.csproj", "{348DC349-4DA9-4AF6-BA4F-A65BE8C10A86}" 23 | EndProject 24 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GoogleCast", "GoogleCast\GoogleCast.csproj", "{A17F749E-C883-458B-AB82-57B49DDC4D73}" 25 | EndProject 26 | Global 27 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 28 | Debug|Any CPU = Debug|Any CPU 29 | Debug|x86 = Debug|x86 30 | Release|Any CPU = Release|Any CPU 31 | Release|x86 = Release|x86 32 | EndGlobalSection 33 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 34 | {F5D46BA1-6F21-40EF-9695-46105CCACD08}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 35 | {F5D46BA1-6F21-40EF-9695-46105CCACD08}.Debug|Any CPU.Build.0 = Debug|Any CPU 36 | {F5D46BA1-6F21-40EF-9695-46105CCACD08}.Debug|x86.ActiveCfg = Debug|x86 37 | {F5D46BA1-6F21-40EF-9695-46105CCACD08}.Debug|x86.Build.0 = Debug|x86 38 | {F5D46BA1-6F21-40EF-9695-46105CCACD08}.Release|Any CPU.ActiveCfg = Release|Any CPU 39 | {F5D46BA1-6F21-40EF-9695-46105CCACD08}.Release|Any CPU.Build.0 = Release|Any CPU 40 | {F5D46BA1-6F21-40EF-9695-46105CCACD08}.Release|x86.ActiveCfg = Release|x86 41 | {F5D46BA1-6F21-40EF-9695-46105CCACD08}.Release|x86.Build.0 = Release|x86 42 | {359FF0BF-12BB-4DF4-B550-D68FFA9D1E9C}.Debug|Any CPU.ActiveCfg = Release|x86 43 | {359FF0BF-12BB-4DF4-B550-D68FFA9D1E9C}.Debug|x86.ActiveCfg = Release|x86 44 | {359FF0BF-12BB-4DF4-B550-D68FFA9D1E9C}.Release|Any CPU.ActiveCfg = Release|x86 45 | {359FF0BF-12BB-4DF4-B550-D68FFA9D1E9C}.Release|x86.ActiveCfg = Release|x86 46 | {348DC349-4DA9-4AF6-BA4F-A65BE8C10A86}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 47 | {348DC349-4DA9-4AF6-BA4F-A65BE8C10A86}.Debug|x86.ActiveCfg = Debug|Any CPU 48 | {348DC349-4DA9-4AF6-BA4F-A65BE8C10A86}.Debug|x86.Build.0 = Debug|Any CPU 49 | {348DC349-4DA9-4AF6-BA4F-A65BE8C10A86}.Release|Any CPU.ActiveCfg = Release|Any CPU 50 | {348DC349-4DA9-4AF6-BA4F-A65BE8C10A86}.Release|x86.ActiveCfg = Release|Any CPU 51 | {348DC349-4DA9-4AF6-BA4F-A65BE8C10A86}.Release|x86.Build.0 = Release|Any CPU 52 | {A17F749E-C883-458B-AB82-57B49DDC4D73}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 53 | {A17F749E-C883-458B-AB82-57B49DDC4D73}.Debug|Any CPU.Build.0 = Debug|Any CPU 54 | {A17F749E-C883-458B-AB82-57B49DDC4D73}.Debug|x86.ActiveCfg = Debug|Any CPU 55 | {A17F749E-C883-458B-AB82-57B49DDC4D73}.Debug|x86.Build.0 = Debug|Any CPU 56 | {A17F749E-C883-458B-AB82-57B49DDC4D73}.Release|Any CPU.ActiveCfg = Release|Any CPU 57 | {A17F749E-C883-458B-AB82-57B49DDC4D73}.Release|Any CPU.Build.0 = Release|Any CPU 58 | {A17F749E-C883-458B-AB82-57B49DDC4D73}.Release|x86.ActiveCfg = Release|Any CPU 59 | {A17F749E-C883-458B-AB82-57B49DDC4D73}.Release|x86.Build.0 = Release|Any CPU 60 | EndGlobalSection 61 | GlobalSection(SolutionProperties) = preSolution 62 | HideSolutionNode = FALSE 63 | EndGlobalSection 64 | GlobalSection(ExtensibilityGlobals) = postSolution 65 | SolutionGuid = {987091D2-CF4E-4304-B7C5-AB0DCE33E467} 66 | EndGlobalSection 67 | EndGlobal 68 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MusicBeeChromecast 2 | Adds Cast functionality to MusicBee 3 | 4 | **Disclaimer:** I originally didn't intend to release this plugin as I won't have more time to work on it other than bare functionality, however maybe someone else will find this useful so here I am. As a result, this plugin is here "AS-IS". That being said, the plugin is fairly easy to follow along, so feel free to fork this repo and make changes if you like. Just make sure you credit the people who made the packages, as well as me. 5 | 6 | ![UI](https://raw.githubusercontent.com/TroyFernandes/MusicBeeChromecast/master/Images/UI.jpg) 7 | 8 | # Features 9 | 1. Standard controls from the Chromecast such as play, pause, next, previous 10 | 2. Display music information such as artist, album, album art, queue status, file info, and next songs on chromecast devices with an attached display. 11 | 3. Use the Google voice assistant to control media (Next, Previous, Seek) 12 | 4. Allows casting to speaker groups for multi-room audio 13 | 14 | # Installation 15 | 16 | 1. Go to releases and download the zipped file (MB_Chromecast.zip) 17 | 2. Extract the zip to somewhere temporary (i.e Desktop) 18 | 3. Copy the two .dll files found in Plugins in the extracted folder to your musicbee plugins folder 19 | 4. Open a command prompt window and navigate to the MBCCRules folder in the extracted zip 20 | 5. Run the .exe from the command prompt passing in the port you wish to use eg. ``MBCCRules.exe 8080`` 21 | 22 | The ``MBCCRules.exe`` requires admin privileges, creates an inbound firewall rule for you local network and runs the following command: ``netsh http add urlacl url=http://*:[PORT]/ user=Everyone`` which allows the chromecast to connect to your PC by it's IP 23 | 24 | # Setup 25 | **PLEASE READ CAREFULLY** 26 | 27 | 1. Go to Edit -> Edit Preferences -> Plugins -> Click the "Settings" button under Musicbee Chromecast 28 | 2. Enter a web server port. Use the same port you used for the ``MBCCRules.exe`` NOTE: you must choose a port between 1025-65535 (I recommend 8080) 29 | 4. Click save, and restart musicbee 30 | 5. Right-click the toolbar (or on the "Arrange Panels") icon and click "Configure Toolbar" 31 | 6. Add a new Toolbar button with an icon of your choosing, or simply type in "Chromecast" for example. Under the "Command" dropdown, choose "Chromecast". Then click update. 32 | 33 | # How to Use 34 | The plugin will find all available devices/speaker groups you've created. To use the plugin, click the Toolbar Icon/text you created in the previous steps, a window with all the available devices will pop up. Simply choose one you want to connect to and it should connect successfully. You can go to Tools -> MB Chromecast -> Check status, to see if everything is running. 35 | 36 | # Troubleshooting 37 | 38 | 1. You can go to Tools -> MB Chromecast -> Check status to check the status of the web server and the chromecast connection 39 | 2. If you hear the connection sound, but no music is playing, you most likely have an issue with your port forwarding on the webserver. A good tip to try is after connecting to a chromecast, go on your phone and enter in a browser the 40 | ``:`` e.g ``192.168.1.27:8080``. (You can also find this address under Check Status) If you're able to see the files, then the chromecast will be able to as well 41 | 3. If you get an error when trying to save settings. ``Navigate to C:\Users\\Appdata\Roaming\MusicBee\MB_Chromecast_Settings.xml`` and delete the file. 42 | 4. If you get `The server factory could not be located for the given input: Microsoft.Owin.Host.HttpListener`, try moving the `Microsoft.Owin.Host.HttpListener.dll` to the same folder where the ``MusicBee.exe`` is (i.e up one folder). 43 | 44 | # Libraries Used 45 | 46 | - [GoogleCast](https://github.com/kakone/GoogleCast/tree/master/GoogleCast) + [DayCast](https://github.com/MathieuCyr/DayCast) + [CastIt](https://github.com/Wolfteam/CastIt), with [hig-dev](https://github.com/hig-dev) for his fork 47 | - [Nito.AsyncEx](https://www.nuget.org/packages/Nito.AsyncEx) 48 | - [Microsoft.Owin](https://www.nuget.org/packages/Microsoft.Owin/) 49 | -------------------------------------------------------------------------------- /CAF Receiver/css/style.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Product Sans'; 3 | font-style: normal; 4 | src: local('Open Sans'), local('OpenSans'), url(https://fonts.gstatic.com/s/productsans/v5/HYvgU2fE2nRJvZ5JFAumwegdm0LZdjqr5-oayXSOefg.woff2) format('woff2'); 5 | } 6 | 7 | body { 8 | overflow: hidden; 9 | margin: 0px; 10 | padding: 0px; 11 | } 12 | 13 | video { 14 | height: 100%; 15 | width: 100%; 16 | color: #FFFFFF; 17 | z-index: 1; 18 | position: absolute; 19 | background-color: rgb(12, 12, 12); 20 | /* Add the blur effect */ 21 | filter: blur(7px); 22 | -webkit-filter: blur(7px); 23 | background-repeat: no-repeat; 24 | background-size: cover; 25 | } 26 | 27 | 28 | #messages span { 29 | font-weight: normal; 30 | } 31 | 32 | img { 33 | position: relative; 34 | z-index: 1; 35 | padding: 50px; 36 | box-sizing: border-box; 37 | resize: horizontal; 38 | overflow: auto; 39 | max-width: 100%; 40 | height: calc(100vh - 140px); 41 | margin-top: 2%; 42 | margin-left: 3%; 43 | } 44 | 45 | #splash{ 46 | position: absolute; 47 | font-family: Product Sans; 48 | font: bold; 49 | font-size: 100px; 50 | z-index: 1; 51 | top: 40%; 52 | left: 33%; 53 | color: white; 54 | } 55 | 56 | #totaltime { 57 | font-size: 20px; 58 | font-family: Product Sans; 59 | color: white; 60 | float: left; 61 | margin-left: 90%; 62 | margin-top: 0%; 63 | z-index: 2; 64 | position: absolute; 65 | font-weight: bold; 66 | width: 5%; 67 | height: 3%; 68 | border: 0px solid blue; 69 | text-align: right; 70 | 71 | } 72 | #currenttime { 73 | font-size: 20px; 74 | font-family: Product Sans; 75 | color: white; 76 | float: left; 77 | margin-left: 7.3%; 78 | margin-top: 0%; 79 | z-index: 2; 80 | position: absolute; 81 | font-weight: bold; 82 | width: 5%; 83 | height: 3%; 84 | border: 0px solid blue; 85 | 86 | } 87 | 88 | #tracktitle { 89 | font-size: 65px; 90 | font-family: Product Sans; 91 | color: white; 92 | /* float: left; */ 93 | margin-left: 48%; 94 | margin-top: -36.3%; 95 | z-index: 1; 96 | position: absolute; 97 | width: 39%; 98 | border: 0px solid blue; 99 | font-weight: 800; 100 | display: -webkit-box; 101 | -webkit-line-clamp: 2; 102 | -webkit-box-orient: vertical; 103 | overflow: hidden; 104 | } 105 | 106 | #trackartist { 107 | font-size: 40px; 108 | font-family: Product Sans; 109 | color: white; 110 | float: left; 111 | margin-left: 48%; 112 | margin-top: -24.3%; 113 | z-index: 2; 114 | position: absolute; 115 | font-weight: bold; 116 | width: 39%; 117 | height: 6.5%; 118 | border: 0px solid blue; 119 | font-weight: 200; 120 | overflow: hidden; 121 | text-overflow: ellipsis; 122 | } 123 | 124 | #trackalbum { 125 | font-size: 40px; 126 | font-family: Product Sans; 127 | color: white; 128 | float: left; 129 | margin-left: 48%; 130 | margin-top: -20.55%; 131 | z-index: 2; 132 | position: absolute; 133 | font-weight: bold; 134 | width: 39%; 135 | height: 6.5%; 136 | border: 0px solid blue; 137 | font-weight: 200; 138 | overflow: hidden; 139 | text-overflow: ellipsis; 140 | } 141 | 142 | 143 | #tracksamplerate { 144 | font-size: 20px; 145 | font-family: Product Sans; 146 | color: white; 147 | float: left; 148 | margin-left: 48%; 149 | margin-top: -10.3%; 150 | z-index: 2; 151 | position: absolute; 152 | width: 39%; 153 | height: 3.5%; 154 | border: 0px solid blue; 155 | font-weight: 10; 156 | } 157 | 158 | #trackposition { 159 | font-size: 20px; 160 | font-family: Product Sans; 161 | color: white; 162 | float: left; 163 | margin-left: 48%; 164 | margin-top: -8.3%; 165 | z-index: 2; 166 | position: absolute; 167 | width: 39%; 168 | height: 3.5%; 169 | border: 0px solid blue; 170 | } 171 | #nexttrack { 172 | font-size: 20px; 173 | font-family: Product Sans; 174 | color: white; 175 | float: left; 176 | margin-left: 48%; 177 | margin-top: -6.3%; 178 | z-index: 2; 179 | position: absolute; 180 | width: 39%; 181 | height: 3.5%; 182 | border: 0px solid blue; 183 | } -------------------------------------------------------------------------------- /ChromecastPanel.cs: -------------------------------------------------------------------------------- 1 | using GoogleCast; 2 | using GoogleCast.Channels; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.ComponentModel; 6 | using System.Data; 7 | using System.Drawing; 8 | using System.Linq; 9 | using System.Text; 10 | using System.Threading.Tasks; 11 | using System.Windows.Forms; 12 | 13 | namespace MusicBeePlugin 14 | { 15 | public partial class ChromecastPanel : Form 16 | { 17 | private Color backgroundColor { get; set; } 18 | public IMediaChannel ChromecastMediaChannel { get; set; } = null; 19 | //public Sender ChromecastSender { get; set; } = null; 20 | public bool Disconnect { get; set; } = false; 21 | 22 | public ChromecastPanel(Color color) 23 | { 24 | backgroundColor = color; 25 | InitializeComponent(); 26 | } 27 | 28 | private async void ChromecastPanel_Load(object sender, EventArgs e) 29 | { 30 | 31 | var contrastColor = ContrastColor(backgroundColor); 32 | this.BackColor = backgroundColor; 33 | this.closeText.ForeColor = contrastColor; 34 | this.devicesText.ForeColor = contrastColor; 35 | 36 | 37 | IEnumerable receiver = await new DeviceLocator().FindReceiversAsync(); 38 | 39 | if (receiver.Count() == 0 ) 40 | { 41 | Button b = new Button 42 | { 43 | BackColor = Color.Transparent, 44 | ForeColor = ContrastColor(backgroundColor), 45 | FlatStyle = FlatStyle.Flat, 46 | Text = "No Devices", 47 | AutoSize = false, 48 | Width = 200, 49 | 50 | }; 51 | b.FlatAppearance.MouseOverBackColor = System.Drawing.Color.Transparent; 52 | b.FlatAppearance.BorderColor = backgroundColor; 53 | flowLayoutPanel1.Controls.Add(b); 54 | } 55 | 56 | foreach (var x in receiver) 57 | { 58 | Button b = new Button 59 | { 60 | BackColor = Color.Transparent, 61 | ForeColor = ContrastColor(backgroundColor), 62 | FlatStyle = FlatStyle.Flat, 63 | Text = x.FriendlyName, 64 | AutoSize = false, 65 | Width = 200, 66 | 67 | }; 68 | 69 | b.Click += 70 | new EventHandler((s, e2) => MyButtonHandler(s, e, receiver)); 71 | 72 | b.FlatAppearance.MouseOverBackColor = System.Drawing.Color.Transparent; 73 | b.FlatAppearance.BorderColor = backgroundColor; 74 | 75 | flowLayoutPanel1.Controls.Add(b); 76 | } 77 | 78 | 79 | } 80 | 81 | async void MyButtonHandler(object sender, EventArgs e, IEnumerable devices) 82 | { 83 | 84 | var sender2 = new Sender(); 85 | 86 | IReceiver device = null; 87 | foreach (var x in devices) 88 | { 89 | if (x.FriendlyName == (sender as Button).Text) 90 | { 91 | device = x; 92 | } 93 | } 94 | 95 | if (device != null) 96 | { 97 | //Connect to the device 98 | await sender2.ConnectAsync(device); 99 | //Launch the media reciever app 100 | var mediaChannel = sender2.GetChannel(); 101 | await sender2.LaunchAsync(mediaChannel); 102 | 103 | //ChromecastSender = sender2; 104 | ChromecastMediaChannel = mediaChannel; 105 | 106 | this.FormClosing -= ChromecastSelection_FormClosing; 107 | this.Close(); 108 | } 109 | 110 | } 111 | 112 | 113 | private void label1_Click(object sender, EventArgs e) 114 | { 115 | this.FormClosing -= ChromecastSelection_FormClosing; 116 | this.Close(); 117 | } 118 | 119 | Color ContrastColor(Color color) 120 | { 121 | int d = 0; 122 | 123 | // Counting the perceptive luminance - human eye favors green color... 124 | double luminance = (0.299 * color.R + 0.587 * color.G + 0.114 * color.B) / 255; 125 | 126 | if (luminance > 0.5) 127 | d = 0; // bright colors - black font 128 | else 129 | d = 255; // dark colors - white font 130 | 131 | return Color.FromArgb(d, d, d); 132 | } 133 | 134 | 135 | private void ChromecastSelection_FormClosing(object sender, FormClosingEventArgs e) 136 | { 137 | this.FormClosing -= ChromecastSelection_FormClosing; 138 | this.Close(); 139 | } 140 | 141 | 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /GoogleCast/Models/Media/TextTrackStyle.cs: -------------------------------------------------------------------------------- 1 | using System.Drawing; 2 | using System.Runtime.Serialization; 3 | 4 | namespace GoogleCast.Models.Media 5 | { 6 | /// 7 | /// Describes style information for a text track 8 | /// 9 | [DataContract] 10 | public class TextTrackStyle 11 | { 12 | /// 13 | /// Gets or sets the background 32 bit RGBA color 14 | /// 15 | /// the alpha channel should be used for transparent backgrounds 16 | [IgnoreDataMember] 17 | public Color? BackgroundColor { get; set; } 18 | 19 | [DataMember(Name = "backgroundColor", EmitDefaultValue = false)] 20 | private string BackgroundColorString 21 | { 22 | get => BackgroundColor.ToHexString(); 23 | set => BackgroundColor = ColorHelper.FromNullableHexString(value); 24 | } 25 | 26 | /// 27 | /// Gets or sets the RGBA color for the edge 28 | /// 29 | /// this value will be ignored if EdgeType is None 30 | [IgnoreDataMember] 31 | public Color? EdgeColor { get; set; } 32 | 33 | [DataMember(Name = "edgeColor", EmitDefaultValue = false)] 34 | private string EdgeColorString 35 | { 36 | get => EdgeColor.ToHexString(); 37 | set => EdgeColor = ColorHelper.FromNullableHexString(value); 38 | } 39 | 40 | /// 41 | /// Gets or sets the text track edge type 42 | /// 43 | [IgnoreDataMember] 44 | public TextTrackEdgeType? EdgeType { get; set; } 45 | 46 | [DataMember(Name = "edgeType", EmitDefaultValue = false)] 47 | private string EdgeTypeString 48 | { 49 | get { return EdgeType.GetName(); } 50 | set { EdgeType = EnumHelper.ParseNullable(value); } 51 | } 52 | 53 | /// 54 | /// Gets or sets the font family 55 | /// 56 | /// if the font is not available in the receiver, the fontGenericFamily will be used 57 | [DataMember(Name = "fontFamily", EmitDefaultValue = false)] 58 | public string FontFamily { get; set; } 59 | 60 | /// 61 | /// Gets or sets the text track generic family 62 | /// 63 | [IgnoreDataMember] 64 | public TextTrackFontGenericFamily? FontGenericFamily { get; set; } 65 | 66 | [DataMember(Name = "fontGenericFamily", EmitDefaultValue = false)] 67 | private string FontGenericFamilyString 68 | { 69 | get { return FontGenericFamily.GetName(); } 70 | set { FontGenericFamily = EnumHelper.ParseNullable(value); } 71 | } 72 | 73 | /// 74 | /// Gets or sets font scaling factor for the text track 75 | /// 76 | /// the default is 1 77 | [DataMember(Name = "fontScale", EmitDefaultValue = false)] 78 | public float? FontScale { get; set; } 79 | 80 | /// 81 | /// Gets or sets the text track font style 82 | /// 83 | [IgnoreDataMember] 84 | public TextTrackFontStyle? FontStyle { get; set; } 85 | 86 | [DataMember(Name = "fontStyle", EmitDefaultValue = false)] 87 | private string FontStyleString 88 | { 89 | get { return FontStyle.GetName(); } 90 | set { FontStyle = EnumHelper.ParseNullable(value); } 91 | } 92 | 93 | /// 94 | /// Gets or sets the foreground 32 bit RGBA color 95 | /// 96 | [IgnoreDataMember] 97 | public Color? ForegroundColor { get; set; } 98 | 99 | [DataMember(Name = "foregroundColor", EmitDefaultValue = false)] 100 | private string ForegroundColorString 101 | { 102 | get => ForegroundColor.ToHexString(); 103 | set => ForegroundColor = ColorHelper.FromNullableHexString(value); 104 | } 105 | 106 | /// 107 | /// Gets or sets the 32 bit RGBA color for the window 108 | /// 109 | /// this value will be ignored if WindowType is None 110 | [IgnoreDataMember] 111 | public Color? WindowColor { get; set; } 112 | 113 | [DataMember(Name = "windowColor", EmitDefaultValue = false)] 114 | private string WindowColorColorString 115 | { 116 | get => WindowColor.ToHexString(); 117 | set => WindowColor = ColorHelper.FromNullableHexString(value); 118 | } 119 | 120 | /// 121 | /// Gets or sets the rounded corner radius absolute value in pixels (px) 122 | /// 123 | /// this value will be ignored if windowType is not RoundedCorners 124 | [DataMember(Name = "windowRoundedCornerRadius", EmitDefaultValue = false)] 125 | public ushort? WindowRoundedCornerRadius { get; set; } 126 | 127 | /// 128 | /// Gets or sets the window type 129 | /// 130 | /// the window concept is defined in CEA-608 and CEA-708. In WebVTT is called a region 131 | [DataMember(Name = "windowType", EmitDefaultValue = false)] 132 | public TextTrackWindowType? WindowType { get; set; } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /Settings.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace MusicBeePlugin 2 | { 3 | partial class Settings 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Windows Form Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | this.label1 = new System.Windows.Forms.Label(); 32 | this.serverPortSelect = new System.Windows.Forms.NumericUpDown(); 33 | this.closeText = new System.Windows.Forms.Label(); 34 | this.label3 = new System.Windows.Forms.Label(); 35 | this.saveText = new System.Windows.Forms.Label(); 36 | ((System.ComponentModel.ISupportInitialize)(this.serverPortSelect)).BeginInit(); 37 | this.SuspendLayout(); 38 | // 39 | // label1 40 | // 41 | this.label1.AutoSize = true; 42 | this.label1.Location = new System.Drawing.Point(21, 40); 43 | this.label1.Name = "label1"; 44 | this.label1.Size = new System.Drawing.Size(86, 13); 45 | this.label1.TabIndex = 1; 46 | this.label1.Text = "Web Server Port"; 47 | // 48 | // serverPortSelect 49 | // 50 | this.serverPortSelect.Location = new System.Drawing.Point(113, 38); 51 | this.serverPortSelect.Maximum = new decimal(new int[] { 52 | 65535, 53 | 0, 54 | 0, 55 | 0}); 56 | this.serverPortSelect.Minimum = new decimal(new int[] { 57 | 1025, 58 | 0, 59 | 0, 60 | 0}); 61 | this.serverPortSelect.Name = "serverPortSelect"; 62 | this.serverPortSelect.Size = new System.Drawing.Size(59, 20); 63 | this.serverPortSelect.TabIndex = 2; 64 | this.serverPortSelect.Value = new decimal(new int[] { 65 | 8080, 66 | 0, 67 | 0, 68 | 0}); 69 | // 70 | // closeText 71 | // 72 | this.closeText.AutoSize = true; 73 | this.closeText.Location = new System.Drawing.Point(204, 62); 74 | this.closeText.Name = "closeText"; 75 | this.closeText.Size = new System.Drawing.Size(33, 13); 76 | this.closeText.TabIndex = 6; 77 | this.closeText.Text = "Close"; 78 | this.closeText.Click += new System.EventHandler(this.closeText_Click); 79 | // 80 | // label3 81 | // 82 | this.label3.AutoSize = true; 83 | this.label3.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); 84 | this.label3.Location = new System.Drawing.Point(12, 9); 85 | this.label3.Name = "label3"; 86 | this.label3.Size = new System.Drawing.Size(111, 16); 87 | this.label3.TabIndex = 7; 88 | this.label3.Text = "Plugin Settings"; 89 | // 90 | // saveText 91 | // 92 | this.saveText.AutoSize = true; 93 | this.saveText.Location = new System.Drawing.Point(12, 62); 94 | this.saveText.Name = "saveText"; 95 | this.saveText.Size = new System.Drawing.Size(32, 13); 96 | this.saveText.TabIndex = 8; 97 | this.saveText.Text = "Save"; 98 | this.saveText.Click += new System.EventHandler(this.saveText_Click); 99 | // 100 | // Settings 101 | // 102 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 103 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 104 | this.ClientSize = new System.Drawing.Size(251, 91); 105 | this.Controls.Add(this.saveText); 106 | this.Controls.Add(this.label3); 107 | this.Controls.Add(this.closeText); 108 | this.Controls.Add(this.serverPortSelect); 109 | this.Controls.Add(this.label1); 110 | this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None; 111 | this.Name = "Settings"; 112 | this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; 113 | this.Text = "Settings"; 114 | ((System.ComponentModel.ISupportInitialize)(this.serverPortSelect)).EndInit(); 115 | this.ResumeLayout(false); 116 | this.PerformLayout(); 117 | 118 | } 119 | 120 | #endregion 121 | 122 | private System.Windows.Forms.Label label1; 123 | private System.Windows.Forms.NumericUpDown serverPortSelect; 124 | private System.Windows.Forms.Label closeText; 125 | private System.Windows.Forms.Label label3; 126 | private System.Windows.Forms.Label saveText; 127 | } 128 | } -------------------------------------------------------------------------------- /GoogleCast/Channels/ReceiverChannel.cs: -------------------------------------------------------------------------------- 1 | using GoogleCast.Messages.Receiver; 2 | using GoogleCast.Models; 3 | using GoogleCast.Models.Receiver; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | 8 | namespace GoogleCast.Channels 9 | { 10 | /// 11 | /// Receiver channel 12 | /// 13 | class ReceiverChannel : StatusChannel, IReceiverChannel 14 | { 15 | /// 16 | /// Initializes a new instance of class 17 | /// 18 | public ReceiverChannel() : base("receiver") 19 | { 20 | } 21 | 22 | /// 23 | /// Gets or sets the sender 24 | /// 25 | public override ISender Sender 26 | { 27 | set 28 | { 29 | var sender = Sender; 30 | if (sender != value) 31 | { 32 | if (sender != null) 33 | { 34 | sender.Disconnected -= Disconnected; 35 | } 36 | base.Sender = value; 37 | if (value != null) 38 | { 39 | value.Disconnected += Disconnected; 40 | } 41 | } 42 | } 43 | } 44 | 45 | private bool IsConnected { get; set; } 46 | 47 | /// 48 | /// Launches an application 49 | /// 50 | /// application identifier 51 | /// receiver status 52 | public async Task LaunchAsync(string applicationId) 53 | { 54 | return (await SendAsync(new LaunchMessage() { ApplicationId = applicationId })).Status; 55 | } 56 | 57 | /// 58 | /// Sets the volume 59 | /// 60 | /// volume level (0.0 to 1.0) 61 | /// receiver status 62 | public async Task SetVolumeAsync(float level) 63 | { 64 | return await SetVolumeAsync(level, null); 65 | } 66 | 67 | /// 68 | /// Sets a value indicating whether the audio should be muted 69 | /// 70 | /// true if audio should be muted; otherwise, false 71 | /// receiver status 72 | public async Task SetIsMutedAsync(bool isMuted) 73 | { 74 | return await SetVolumeAsync(null, isMuted); 75 | } 76 | 77 | private async Task SetVolumeAsync(float? level, bool? isMuted) 78 | { 79 | return (await SendAsync( 80 | new SetVolumeMessage() 81 | { 82 | Volume = new Volume() 83 | { 84 | Level = level, 85 | IsMuted = isMuted, 86 | } 87 | })).Status; 88 | } 89 | 90 | private async Task CheckStatusAsync() 91 | { 92 | var status = Status; 93 | if (status == null) 94 | { 95 | status = await GetStatusAsync(); 96 | } 97 | return status; 98 | } 99 | 100 | /// 101 | /// Checks the connection is well established 102 | /// 103 | /// namespace 104 | /// an application object 105 | public async Task EnsureConnectionAsync(string ns) 106 | { 107 | var status = await CheckStatusAsync(); 108 | var application = status.Applications.FirstOrDefault(a => a.Namespaces.Any(n => n.Name == ns)); 109 | if (!IsConnected) 110 | { 111 | await Sender.GetChannel().ConnectAsync(application.TransportId); 112 | IsConnected = true; 113 | } 114 | return application; 115 | } 116 | 117 | private void Disconnected(object sender, System.EventArgs e) 118 | { 119 | IsConnected = false; 120 | } 121 | 122 | /// 123 | /// Stops the current applications 124 | /// 125 | /// applications to stop 126 | /// ReceiverStatus 127 | public async Task StopAsync(params Application[] applications) 128 | { 129 | IEnumerable apps = applications; 130 | if (apps == null || !applications.Any()) 131 | { 132 | apps = (await CheckStatusAsync()).Applications; 133 | if (apps == null || !applications.Any()) 134 | { 135 | return null; 136 | } 137 | } 138 | 139 | ReceiverStatusMessage receiverStatusMessage = null; 140 | foreach (var application in apps) 141 | { 142 | receiverStatusMessage = await SendAsync(new StopMessage() { SessionId = application.SessionId }); 143 | } 144 | return receiverStatusMessage.Status; 145 | } 146 | 147 | /// 148 | /// Retrieves the status 149 | /// 150 | /// the status 151 | public async Task GetStatusAsync() 152 | { 153 | return (await SendAsync(new GetStatusMessage())).Status; 154 | } 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /CAF Receiver/build/receiver.html: -------------------------------------------------------------------------------- 1 | >
Musicbee
-------------------------------------------------------------------------------- /Settings.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | -------------------------------------------------------------------------------- /ChromecastPanel.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | -------------------------------------------------------------------------------- /Properties/Resources.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | -------------------------------------------------------------------------------- /GoogleCast/Channels/IMediaChannel.cs: -------------------------------------------------------------------------------- 1 | using GoogleCast.Models.Media; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Threading.Tasks; 5 | 6 | namespace GoogleCast.Channels 7 | { 8 | /// 9 | /// Interface for the media channel 10 | /// 11 | public interface IMediaChannel : IStatusChannel>, IApplicationChannel 12 | { 13 | /// 14 | /// Raised when the status has changed 15 | /// 16 | event EventHandler QueueStatusChanged; 17 | 18 | event EventHandler NextRequested; 19 | event EventHandler PreviousRequested; 20 | 21 | /// 22 | /// Gets the status 23 | /// 24 | QueueStatus QueueStatus { get; } 25 | 26 | /// 27 | /// Retrieves the media status 28 | /// 29 | /// the media status 30 | Task GetStatusAsync(); 31 | 32 | /// 33 | /// Loads a media 34 | /// 35 | /// media to load 36 | /// true to play the media directly, false otherwise 37 | /// tracks identifiers that should be active 38 | /// media status 39 | Task LoadAsync(MediaInformation media, bool autoPlay = true, params int[] activeTrackIds); 40 | 41 | /// 42 | /// Loads a queue items 43 | /// 44 | /// queue repeat mode 45 | /// media items 46 | /// media status 47 | Task QueueLoadAsync(RepeatMode repeatMode, params MediaInformation[] medias); 48 | 49 | /// 50 | /// Loads a queue items 51 | /// 52 | /// queue repeat mode 53 | /// items to load 54 | /// media status 55 | Task QueueLoadAsync(RepeatMode repeatMode, params QueueItem[] queueItems); 56 | 57 | /// 58 | /// Inserts queue items into the queue 59 | /// 60 | /// items to insert 61 | /// media status 62 | Task QueueInsertAsync(QueueItem[] queueItems); 63 | 64 | /// 65 | /// Removes queue items from the queue 66 | /// 67 | /// item ids to remove 68 | /// media status 69 | Task QueueRemoveAsync(int[] queueItemIds); 70 | 71 | /// 72 | /// Updates the queue with new currently playing media and shuffle 73 | /// 74 | /// item id to set currently playing media 75 | /// bool 76 | /// media status 77 | Task QueueUpdateAsync(int? currentItemId = null, bool? shuffle = null); 78 | 79 | /// 80 | /// Reorders queue items in the queue 81 | /// 82 | /// item ids to reorder 83 | /// media status 84 | Task QueueReorderAsync(int[] queueItemIds); 85 | 86 | /// 87 | /// Get the given item ids' info 88 | /// 89 | /// item ids to retrieve info 90 | /// media status 91 | Task QueueGetItemsMessage(int[] itemIds); 92 | 93 | /// 94 | /// Get all currently queued item ids 95 | /// 96 | /// int array 97 | Task QueueGetItemIdsMessage(); 98 | 99 | /// 100 | /// Edits tracks info 101 | /// 102 | /// true to enable text tracks, false otherwise 103 | /// language for the tracks that should be active 104 | /// track identifiers that should be active 105 | /// media status 106 | Task EditTracksInfoAsync(string language = null, bool enabledTextTracks = true, params int[] activeTrackIds); 107 | 108 | /// 109 | /// Plays the media 110 | /// 111 | /// media status 112 | Task PlayAsync(); 113 | 114 | /// 115 | /// Pauses the media 116 | /// 117 | /// media status 118 | Task PauseAsync(); 119 | 120 | /// 121 | /// Stops the media 122 | /// 123 | /// media status 124 | Task StopAsync(); 125 | 126 | /// 127 | /// Seeks to the specified time 128 | /// 129 | /// time in seconds 130 | /// media status 131 | Task SeekAsync(double seconds); 132 | 133 | /// 134 | /// Sets the current playback rate of the stream 135 | /// 136 | /// playback rate 137 | /// media status 138 | Task SetPlaybackRateMessage(double playbackRate); 139 | 140 | /// 141 | /// Skips to the next media in the queue 142 | /// 143 | /// media status 144 | Task NextAsync(); 145 | 146 | /// 147 | /// Skips to the previous media in the queue 148 | /// 149 | /// media status 150 | Task PreviousAsync(); 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /CAF Receiver/receiver.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 |
Musicbee
17 |
18 | 19 |
20 | 21 |
22 | 23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | 32 | 163 | 164 | 165 | 166 | 167 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Aa][Rr][Mm]/ 27 | [Aa][Rr][Mm]64/ 28 | bld/ 29 | [Bb]in/ 30 | [Oo]bj/ 31 | [Ll]og/ 32 | [Ll]ogs/ 33 | 34 | # Visual Studio 2015/2017 cache/options directory 35 | .vs/ 36 | # Uncomment if you have tasks that create the project's static files in wwwroot 37 | #wwwroot/ 38 | 39 | # Visual Studio 2017 auto generated files 40 | Generated\ Files/ 41 | 42 | # MSTest test Results 43 | [Tt]est[Rr]esult*/ 44 | [Bb]uild[Ll]og.* 45 | 46 | # NUnit 47 | *.VisualState.xml 48 | TestResult.xml 49 | nunit-*.xml 50 | 51 | # Build Results of an ATL Project 52 | [Dd]ebugPS/ 53 | [Rr]eleasePS/ 54 | dlldata.c 55 | 56 | # Benchmark Results 57 | BenchmarkDotNet.Artifacts/ 58 | 59 | # .NET Core 60 | project.lock.json 61 | project.fragment.lock.json 62 | artifacts/ 63 | 64 | # StyleCop 65 | StyleCopReport.xml 66 | 67 | # Files built by Visual Studio 68 | *_i.c 69 | *_p.c 70 | *_h.h 71 | *.ilk 72 | *.meta 73 | *.obj 74 | *.iobj 75 | *.pch 76 | *.pdb 77 | *.ipdb 78 | *.pgc 79 | *.pgd 80 | *.rsp 81 | *.sbr 82 | *.tlb 83 | *.tli 84 | *.tlh 85 | *.tmp 86 | *.tmp_proj 87 | *_wpftmp.csproj 88 | *.log 89 | *.vspscc 90 | *.vssscc 91 | .builds 92 | *.pidb 93 | *.svclog 94 | *.scc 95 | 96 | # Chutzpah Test files 97 | _Chutzpah* 98 | 99 | # Visual C++ cache files 100 | ipch/ 101 | *.aps 102 | *.ncb 103 | *.opendb 104 | *.opensdf 105 | *.sdf 106 | *.cachefile 107 | *.VC.db 108 | *.VC.VC.opendb 109 | 110 | # Visual Studio profiler 111 | *.psess 112 | *.vsp 113 | *.vspx 114 | *.sap 115 | 116 | # Visual Studio Trace Files 117 | *.e2e 118 | 119 | # TFS 2012 Local Workspace 120 | $tf/ 121 | 122 | # Guidance Automation Toolkit 123 | *.gpState 124 | 125 | # ReSharper is a .NET coding add-in 126 | _ReSharper*/ 127 | *.[Rr]e[Ss]harper 128 | *.DotSettings.user 129 | 130 | # TeamCity is a build add-in 131 | _TeamCity* 132 | 133 | # DotCover is a Code Coverage Tool 134 | *.dotCover 135 | 136 | # AxoCover is a Code Coverage Tool 137 | .axoCover/* 138 | !.axoCover/settings.json 139 | 140 | # Coverlet is a free, cross platform Code Coverage Tool 141 | coverage*[.json, .xml, .info] 142 | 143 | # Visual Studio code coverage results 144 | *.coverage 145 | *.coveragexml 146 | 147 | # NCrunch 148 | _NCrunch_* 149 | .*crunch*.local.xml 150 | nCrunchTemp_* 151 | 152 | # MightyMoose 153 | *.mm.* 154 | AutoTest.Net/ 155 | 156 | # Web workbench (sass) 157 | .sass-cache/ 158 | 159 | # Installshield output folder 160 | [Ee]xpress/ 161 | 162 | # DocProject is a documentation generator add-in 163 | DocProject/buildhelp/ 164 | DocProject/Help/*.HxT 165 | DocProject/Help/*.HxC 166 | DocProject/Help/*.hhc 167 | DocProject/Help/*.hhk 168 | DocProject/Help/*.hhp 169 | DocProject/Help/Html2 170 | DocProject/Help/html 171 | 172 | # Click-Once directory 173 | publish/ 174 | 175 | # Publish Web Output 176 | *.[Pp]ublish.xml 177 | *.azurePubxml 178 | # Note: Comment the next line if you want to checkin your web deploy settings, 179 | # but database connection strings (with potential passwords) will be unencrypted 180 | *.pubxml 181 | *.publishproj 182 | 183 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 184 | # checkin your Azure Web App publish settings, but sensitive information contained 185 | # in these scripts will be unencrypted 186 | PublishScripts/ 187 | 188 | # NuGet Packages 189 | *.nupkg 190 | # NuGet Symbol Packages 191 | *.snupkg 192 | # The packages folder can be ignored because of Package Restore 193 | **/[Pp]ackages/* 194 | # except build/, which is used as an MSBuild target. 195 | !**/[Pp]ackages/build/ 196 | # Uncomment if necessary however generally it will be regenerated when needed 197 | #!**/[Pp]ackages/repositories.config 198 | # NuGet v3's project.json files produces more ignorable files 199 | *.nuget.props 200 | *.nuget.targets 201 | 202 | # Microsoft Azure Build Output 203 | csx/ 204 | *.build.csdef 205 | 206 | # Microsoft Azure Emulator 207 | ecf/ 208 | rcf/ 209 | 210 | # Windows Store app package directories and files 211 | AppPackages/ 212 | BundleArtifacts/ 213 | Package.StoreAssociation.xml 214 | _pkginfo.txt 215 | *.appx 216 | *.appxbundle 217 | *.appxupload 218 | 219 | # Visual Studio cache files 220 | # files ending in .cache can be ignored 221 | *.[Cc]ache 222 | # but keep track of directories ending in .cache 223 | !?*.[Cc]ache/ 224 | 225 | # Others 226 | ClientBin/ 227 | ~$* 228 | *~ 229 | *.dbmdl 230 | *.dbproj.schemaview 231 | *.jfm 232 | *.pfx 233 | *.publishsettings 234 | orleans.codegen.cs 235 | 236 | # Including strong name files can present a security risk 237 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 238 | #*.snk 239 | 240 | # Since there are multiple workflows, uncomment next line to ignore bower_components 241 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 242 | #bower_components/ 243 | 244 | # RIA/Silverlight projects 245 | Generated_Code/ 246 | 247 | # Backup & report files from converting an old project file 248 | # to a newer Visual Studio version. Backup files are not needed, 249 | # because we have git ;-) 250 | _UpgradeReport_Files/ 251 | Backup*/ 252 | UpgradeLog*.XML 253 | UpgradeLog*.htm 254 | ServiceFabricBackup/ 255 | *.rptproj.bak 256 | 257 | # SQL Server files 258 | *.mdf 259 | *.ldf 260 | *.ndf 261 | 262 | # Business Intelligence projects 263 | *.rdl.data 264 | *.bim.layout 265 | *.bim_*.settings 266 | *.rptproj.rsuser 267 | *- [Bb]ackup.rdl 268 | *- [Bb]ackup ([0-9]).rdl 269 | *- [Bb]ackup ([0-9][0-9]).rdl 270 | 271 | # Microsoft Fakes 272 | FakesAssemblies/ 273 | 274 | # GhostDoc plugin setting file 275 | *.GhostDoc.xml 276 | 277 | # Node.js Tools for Visual Studio 278 | .ntvs_analysis.dat 279 | node_modules/ 280 | 281 | # Visual Studio 6 build log 282 | *.plg 283 | 284 | # Visual Studio 6 workspace options file 285 | *.opt 286 | 287 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 288 | *.vbw 289 | 290 | # Visual Studio LightSwitch build output 291 | **/*.HTMLClient/GeneratedArtifacts 292 | **/*.DesktopClient/GeneratedArtifacts 293 | **/*.DesktopClient/ModelManifest.xml 294 | **/*.Server/GeneratedArtifacts 295 | **/*.Server/ModelManifest.xml 296 | _Pvt_Extensions 297 | 298 | # Paket dependency manager 299 | .paket/paket.exe 300 | paket-files/ 301 | 302 | # FAKE - F# Make 303 | .fake/ 304 | 305 | # CodeRush personal settings 306 | .cr/personal 307 | 308 | # Python Tools for Visual Studio (PTVS) 309 | __pycache__/ 310 | *.pyc 311 | 312 | # Cake - Uncomment if you are using it 313 | # tools/** 314 | # !tools/packages.config 315 | 316 | # Tabs Studio 317 | *.tss 318 | 319 | # Telerik's JustMock configuration file 320 | *.jmconfig 321 | 322 | # BizTalk build output 323 | *.btp.cs 324 | *.btm.cs 325 | *.odx.cs 326 | *.xsd.cs 327 | 328 | # OpenCover UI analysis results 329 | OpenCover/ 330 | 331 | # Azure Stream Analytics local run output 332 | ASALocalRun/ 333 | 334 | # MSBuild Binary and Structured Log 335 | *.binlog 336 | 337 | # NVidia Nsight GPU debugger configuration file 338 | *.nvuser 339 | 340 | # MFractors (Xamarin productivity tool) working folder 341 | .mfractor/ 342 | 343 | # Local History for Visual Studio 344 | .localhistory/ 345 | 346 | # BeatPulse healthcheck temp database 347 | healthchecksdb 348 | 349 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 350 | MigrationBackup/ 351 | 352 | # Ionide (cross platform F# VS Code tools) working folder 353 | .ionide/ -------------------------------------------------------------------------------- /GoogleCast/Channels/MediaChannel.cs: -------------------------------------------------------------------------------- 1 | using GoogleCast.Messages; 2 | using GoogleCast.Messages.Media; 3 | using GoogleCast.Models.Media; 4 | using GoogleCast.Models.Receiver; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Threading.Tasks; 9 | 10 | namespace GoogleCast.Channels 11 | { 12 | /// 13 | /// Media channel 14 | /// 15 | class MediaChannel : StatusChannel, MediaStatusMessage>, IMediaChannel 16 | { 17 | /// 18 | /// Initializes a new instance of class 19 | /// 20 | public MediaChannel() : base("media") 21 | { 22 | } 23 | 24 | private int[] ItemIds { get; set; } 25 | private QueueItem[] Items { get; set; } 26 | 27 | public QueueStatus QueueStatus { get; set; } 28 | 29 | public event EventHandler QueueStatusChanged; 30 | 31 | public event EventHandler NextRequested; 32 | public event EventHandler PreviousRequested; 33 | 34 | 35 | /// 36 | /// Gets the application identifier 37 | /// 38 | public string ApplicationId { get; } = "CC1AD845"; 39 | 40 | private Task GetApplicationAsync() 41 | { 42 | return Sender.GetChannel().EnsureConnectionAsync(Namespace); 43 | } 44 | 45 | private Task SendAsync(MediaSessionMessage message, bool mediaSessionIdRequired = true) 46 | { 47 | var mediaSessionId = Status?.FirstOrDefault().MediaSessionId; 48 | if (mediaSessionIdRequired && mediaSessionId == null) 49 | { 50 | throw new ArgumentNullException("MediaSessionId"); 51 | } 52 | message.MediaSessionId = mediaSessionId; 53 | return SendAsync((IMessageWithId)message); 54 | } 55 | 56 | private async Task SendAsync(IMessageWithId message) 57 | { 58 | try 59 | { 60 | return (await SendAsync(message, (await GetApplicationAsync()).TransportId)).Status?.FirstOrDefault(); 61 | } 62 | catch (Exception) 63 | { 64 | Status = null; 65 | throw; 66 | } 67 | } 68 | 69 | private async Task SendAsync(QueueGetItemIdsMessage message) 70 | { 71 | var mediaSessionId = Status?.FirstOrDefault().MediaSessionId; 72 | message.MediaSessionId = mediaSessionId ?? throw new ArgumentNullException("MediaSessionId"); 73 | await SendAsync(message, (await GetApplicationAsync()).TransportId); 74 | return ItemIds; 75 | } 76 | 77 | private async Task SendAsync(QueueGetItemsMessage message) 78 | { 79 | var mediaSessionId = Status?.FirstOrDefault().MediaSessionId; 80 | message.MediaSessionId = mediaSessionId ?? throw new ArgumentNullException("MediaSessionId"); 81 | await SendAsync(message, (await GetApplicationAsync()).TransportId); 82 | return Items; 83 | } 84 | 85 | /// 86 | /// Retrieves the status 87 | /// 88 | /// the status 89 | public Task GetStatusAsync() 90 | { 91 | return SendAsync(new GetStatusMessage() { MediaSessionId = Status?.FirstOrDefault().MediaSessionId }, false); 92 | } 93 | 94 | /// 95 | /// Loads a media 96 | /// 97 | /// media to load 98 | /// true to play the media directly, false otherwise 99 | /// track identifiers that should be active 100 | /// media status 101 | public async Task LoadAsync(MediaInformation media, bool autoPlay = true, params int[] activeTrackIds) 102 | { 103 | return await SendAsync(new LoadMessage() 104 | { 105 | Media = media, 106 | AutoPlay = autoPlay, 107 | ActiveTrackIds = activeTrackIds, 108 | SessionId = (await GetApplicationAsync()).SessionId 109 | }); 110 | } 111 | 112 | /// 113 | /// Loads a queue items 114 | /// 115 | /// queue repeat mode 116 | /// media items 117 | /// media status 118 | public Task QueueLoadAsync(RepeatMode repeatMode, params MediaInformation[] medias) 119 | { 120 | return QueueLoadAsync(repeatMode, medias.Select(mi => new QueueItem() { Media = mi })); 121 | } 122 | 123 | /// 124 | /// Loads a queue items 125 | /// 126 | /// queue repeat mode 127 | /// items to load 128 | /// media status 129 | public Task QueueLoadAsync(RepeatMode repeatMode, params QueueItem[] queueItems) 130 | { 131 | return QueueLoadAsync(repeatMode, queueItems as IEnumerable); 132 | } 133 | 134 | /// 135 | /// Loads a queue items 136 | /// 137 | /// queue repeat mode 138 | /// items to load 139 | /// media status 140 | private async Task QueueLoadAsync(RepeatMode repeatMode, IEnumerable queueItems) 141 | { 142 | return await SendAsync(new QueueLoadMessage() 143 | { 144 | RepeatMode = repeatMode, 145 | Items = queueItems 146 | }); 147 | } 148 | 149 | /// 150 | /// Inserts queue items into the queue 151 | /// 152 | /// items to insert 153 | /// media status 154 | public async Task QueueInsertAsync(QueueItem[] queueItems) 155 | { 156 | return await SendAsync(new QueueInsertMessage() 157 | { 158 | Items = queueItems 159 | }); 160 | } 161 | 162 | /// 163 | /// Removes queue items from the queue 164 | /// 165 | /// item ids to remove 166 | /// media status 167 | public async Task QueueRemoveAsync(int[] queueItemIds) 168 | { 169 | return await SendAsync(new QueueRemoveMessage() 170 | { 171 | ItemIds = queueItemIds 172 | }); 173 | } 174 | 175 | /// 176 | /// Updates the queue with new currently playing media and shuffle 177 | /// 178 | /// item id to set currently playing media 179 | /// bool 180 | /// media status 181 | public async Task QueueUpdateAsync(int? currentItemId = null, bool? shuffle = null) 182 | { 183 | return await SendAsync(new QueueUpdateMessage() 184 | { 185 | CurrentItemId = currentItemId, 186 | Shuffle = shuffle 187 | }); 188 | } 189 | 190 | /// 191 | /// Reorder queue items in the queue 192 | /// 193 | /// item ids to reorder 194 | /// media status 195 | public async Task QueueReorderAsync(int[] queueItemIds) 196 | { 197 | return await SendAsync(new QueueReorderMessage() 198 | { 199 | ItemIds = queueItemIds 200 | }); 201 | } 202 | 203 | /// 204 | /// Get the given item ids' info 205 | /// 206 | /// item ids to retrieve info 207 | /// media status 208 | public async Task QueueGetItemsMessage(int[] itemIds) 209 | { 210 | return await SendAsync(new QueueGetItemsMessage() 211 | { 212 | ItemIds = itemIds 213 | }); 214 | } 215 | 216 | /// 217 | /// Get all currently queued item ids 218 | /// 219 | /// int array 220 | public async Task QueueGetItemIdsMessage() 221 | { 222 | return await SendAsync(new QueueGetItemIdsMessage()); 223 | } 224 | 225 | /// 226 | /// Edits tracks info 227 | /// 228 | /// true to enable text tracks, false otherwise 229 | /// language for the tracks that should be active 230 | /// track identifiers that should be active 231 | /// media status 232 | public async Task EditTracksInfoAsync(string language = null, bool enabledTextTracks = true, params int[] activeTrackIds) 233 | { 234 | return await SendAsync(new EditTracksInfoMessage() 235 | { 236 | Language = language, 237 | EnableTextTracks = enabledTextTracks, 238 | ActiveTrackIds = activeTrackIds 239 | }); 240 | } 241 | 242 | /// 243 | /// Plays the media 244 | /// 245 | /// media status 246 | public async Task PlayAsync() 247 | { 248 | return await SendAsync(new PlayMessage()); 249 | } 250 | 251 | /// 252 | /// Pauses the media 253 | /// 254 | /// media status 255 | public async Task PauseAsync() 256 | { 257 | return await SendAsync(new PauseMessage()); 258 | } 259 | 260 | /// 261 | /// Stops the media 262 | /// 263 | /// media status 264 | public async Task StopAsync() 265 | { 266 | return await SendAsync(new StopMessage()); 267 | } 268 | 269 | /// 270 | /// Seeks to the specified time 271 | /// 272 | /// time in seconds 273 | /// media status 274 | public async Task SeekAsync(double seconds) 275 | { 276 | return await SendAsync(new SeekMessage() { CurrentTime = seconds }); 277 | } 278 | 279 | /// 280 | /// Sets the current playback rate of the stream 281 | /// 282 | /// playback rate 283 | /// media status 284 | public async Task SetPlaybackRateMessage(double playbackRate) 285 | { 286 | return await SendAsync(new SetPlaybackRateMessage() { PlaybackRate = playbackRate }); 287 | } 288 | 289 | /// 290 | /// Skips to the next media in the queue 291 | /// 292 | /// media status 293 | public async Task NextAsync() 294 | { 295 | return await SendAsync(new QueueNextMessage()); 296 | } 297 | 298 | 299 | /// 300 | /// Skips to the previous media in the queue 301 | /// 302 | /// media status 303 | public async Task PreviousAsync() 304 | { 305 | return await SendAsync(new QueuePrevMessage()); 306 | } 307 | 308 | /// 309 | /// Called when a message for this channel is received 310 | /// 311 | /// message to process 312 | public override Task OnMessageReceivedAsync(IMessage message) 313 | { 314 | switch (message) 315 | { 316 | case NextMessage nextMessage: 317 | NextRequested?.Invoke(this, EventArgs.Empty); 318 | break; 319 | 320 | case PreviousMessage prevMessage: 321 | PreviousRequested?.Invoke(this, EventArgs.Empty); 322 | break; 323 | 324 | case QueueItemIdsMessage itemIdsMessage: 325 | ItemIds = itemIdsMessage.ItemIds; 326 | break; 327 | 328 | case QueueItemsMessage itemsMessage: 329 | Items = itemsMessage.Items; 330 | break; 331 | 332 | case QueueChangeMessage changeMessage: 333 | QueueStatus = new QueueStatus() { ChangeTypeString = changeMessage.ChangeType, ItemIds = changeMessage.ItemIds }; 334 | QueueStatusChanged?.Invoke(this, EventArgs.Empty); 335 | break; 336 | } 337 | 338 | return base.OnMessageReceivedAsync(message); 339 | } 340 | 341 | } 342 | } 343 | --------------------------------------------------------------------------------