chromecast 1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ab0190901c9fd66715415de2eded3dea8404e4cb
4
+ data.tar.gz: 3ce530143c686dc4cb515e708ab6b00999af77fd
5
+ SHA512:
6
+ metadata.gz: a6f3a8461d4a2260def97f0f7c7f1c06fe66f4d994e0ec733899c399d1d5d2d0ac9bc53aae4c727a243fa64e5f03c2b190a1b2148e726e04787bf73d516fb9c9
7
+ data.tar.gz: 0b80dd58061653159e6646792aca290fca6f2fa80a49c19f79f5d08c0c3b8da888970ba21e06860f3eaad07a2f7d102cfcbcab2360b34de2fa00e96353493a24
@@ -0,0 +1,47 @@
1
+ = chromecast
2
+
3
+ A simple Ruby library you can use to monitor and control basic features of
4
+ your Chromecast.
5
+
6
+ == Quick start
7
+
8
+ Install the gem:
9
+
10
+ gem install chromecast
11
+
12
+ Or add it to your Gemfile:
13
+
14
+ gem "chromecast"
15
+
16
+ Generate a certificate to be used for TLS connetion:
17
+
18
+ bundle exec rake setup:certificate
19
+
20
+ Use it:
21
+
22
+ require 'chromecast'
23
+
24
+ c = Chromecast::Connection.new(CHROMECAST_IP).open
25
+ c.connection.connect
26
+
27
+ while true
28
+ msg = c.read
29
+
30
+ puts msg
31
+
32
+ if msg['type'] == 'PING'
33
+ c.heartbeat.pong
34
+ end
35
+ end
36
+
37
+ == To Do
38
+
39
+ * Scanning for devices
40
+ * EventMachine support
41
+ * Proper documentation
42
+ * Tests
43
+ * Support for more namespaces
44
+
45
+ == License
46
+
47
+ This gem is released under the {MIT License}[http://www.opensource.org/licenses/MIT].
@@ -0,0 +1,31 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = "chromecast"
3
+ s.version = "1.0"
4
+ s.date = "2014-10-04"
5
+ s.summary = "Basic Chromecast controll via a Ruby library"
6
+ s.description = "The gem lets you monitor Chromecast status, control volume and launch apps."
7
+ s.authors = ["Robert Nasiadek"]
8
+ s.files = [
9
+ "init.rb",
10
+ "lib/chromecast.rb",
11
+ "lib/chromecast/tasks.rb",
12
+ "lib/chromecast/protocol.pb.rb",
13
+ "lib/chromecast/channel/media.rb",
14
+ "lib/chromecast/channel/connection.rb",
15
+ "lib/chromecast/channel/base.rb",
16
+ "lib/chromecast/channel/receiver.rb",
17
+ "lib/chromecast/channel/heartbeat.rb",
18
+ "lib/chromecast/connection.rb",
19
+ "lib/chromecast/application.rb",
20
+ "lib/chromecast/channel.rb",
21
+ "protocol.proto",
22
+ "README.rdoc",
23
+ "chromecast.gemspec"
24
+ ]
25
+ s.email = "robert@kapati.net"
26
+ s.homepage = "http://robzon.pl/"
27
+ s.license = "MIT"
28
+
29
+ s.add_runtime_dependency 'ruby-tls', '~> 1.0'
30
+ s.add_runtime_dependency 'protobuf', '~> 3.3'
31
+ end
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require 'chromecast'
@@ -0,0 +1,13 @@
1
+ require 'rubygems'
2
+ require 'protobuf'
3
+
4
+ require 'chromecast/protocol.pb'
5
+ require 'chromecast/connection'
6
+ require 'chromecast/channel'
7
+ require 'chromecast/application'
8
+
9
+ module Chromecast
10
+ APP_DEFAULT_MEDIA_RECEIVER = 'CC1AD845'
11
+ APP_BACKDROP = 'E8C28D3C'
12
+ APP_YOUTUBE = '233637DE'
13
+ end
@@ -0,0 +1,7 @@
1
+ module Chromecast
2
+ module Application
3
+ DEFAULT_MEDIA_RECEIVER = 'CC1AD845'
4
+ BACKDROP = 'E8C28D3C'
5
+ YOUTUBE = '233637DE'
6
+ end
7
+ end
@@ -0,0 +1,10 @@
1
+ require_relative 'channel/base'
2
+ require_relative 'channel/connection'
3
+ require_relative 'channel/receiver'
4
+ require_relative 'channel/heartbeat'
5
+ require_relative 'channel/media'
6
+
7
+ module Chromecast
8
+ module Channel
9
+ end
10
+ end
@@ -0,0 +1,52 @@
1
+ module Chromecast
2
+ module Channel
3
+ class Base
4
+ attr_accessor :connection, :namespace, :type
5
+
6
+ def initialize connection, namespace, type
7
+ self.connection = connection
8
+ self.namespace = namespace
9
+ self.type = type
10
+ end
11
+
12
+ def send data
13
+ #puts "SEND: #{data}"
14
+ #puts ""
15
+
16
+ msg = new_message(data)
17
+
18
+ encoded = msg.encode
19
+ size = [encoded.size].pack('N')
20
+
21
+ connection.write(size + encoded)
22
+
23
+ return nil
24
+ end
25
+
26
+ private
27
+
28
+ def new_message(payload)
29
+ m = Protocol::CastMessage.new
30
+
31
+ m.protocol_version = Protocol::CastMessage::ProtocolVersion::CASTV2_1_0
32
+ m.source_id = connection.source
33
+ m.destination_id = connection.destination
34
+ m.namespace = namespace
35
+
36
+ case type
37
+ when :json
38
+ m.payload_type = Protocol::CastMessage::PayloadType::STRING
39
+ m.payload_utf8 = payload.to_json
40
+ when :string
41
+ m.payload_type = Protocol::CastMessage::PayloadType::STRING
42
+ m.payload_utf8 = payload
43
+ when :binary
44
+ m.payload_type = Protocol::CastMessage::PayloadType::BINARY
45
+ m.payload_binary = payload
46
+ end
47
+
48
+ return m
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,19 @@
1
+ module Chromecast
2
+ module Channel
3
+ class Connection < Base
4
+ NAMESPACE = 'urn:x-cast:com.google.cast.tp.connection'
5
+
6
+ def initialize connection
7
+ super(connection, NAMESPACE, :json)
8
+ end
9
+
10
+ def connect
11
+ send(type: 'CONNECT')
12
+ end
13
+
14
+ def close
15
+ send(type: 'CLOSE')
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,20 @@
1
+ module Chromecast
2
+ module Channel
3
+ class Heartbeat < Base
4
+ NAMESPACE = 'urn:x-cast:com.google.cast.tp.heartbeat'
5
+
6
+ def initialize connection
7
+ super(connection, NAMESPACE, :json)
8
+ end
9
+
10
+ def ping
11
+ send(type: 'PING')
12
+ end
13
+
14
+ def pong
15
+ send(type: 'PONG')
16
+ end
17
+ end
18
+ end
19
+ end
20
+
@@ -0,0 +1,34 @@
1
+ module Chromecast
2
+ module Channel
3
+ class Media < Base
4
+ NAMESPACE = 'urn:x-cast:com.google.cast.media'
5
+
6
+ def initialize connection
7
+ super(connection, NAMESPACE, :json)
8
+ @request_id = 0
9
+ end
10
+
11
+ def get_status
12
+ send(type: 'GET_STATUS')
13
+ end
14
+
15
+ def pause
16
+ send(type: 'PAUSE')
17
+ end
18
+
19
+ private
20
+
21
+ def next_request_id
22
+ @request_id += 1
23
+ end
24
+
25
+ def send data
26
+ super(data.merge(
27
+ requestId: next_request_id,
28
+ mediaSessionId: '0B5C6066-F8B2-77F4-E5F1-8CEC223F0A22'
29
+ ))
30
+ @request_id
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,51 @@
1
+ module Chromecast
2
+ module Channel
3
+ class Receiver < Base
4
+ NAMESPACE = 'urn:x-cast:com.google.cast.receiver'
5
+
6
+ def initialize connection
7
+ super(connection, NAMESPACE, :json)
8
+ @request_id = 0
9
+ end
10
+
11
+ def launch app_id
12
+ send(type: 'LAUNCH', appId: app_id)
13
+ end
14
+
15
+ def stop session_id
16
+ send(type: 'STOP', sessionId: session_id)
17
+ end
18
+
19
+ def get_status
20
+ send(type: 'GET_STATUS')
21
+ end
22
+
23
+ def get_app_availability apps
24
+ send(type: 'GET_APP_AVAILABILITY', appId: [apps].flatten)
25
+ end
26
+
27
+ def set_volume level
28
+ send(type: 'SET_VOLUME', volume: {level: level})
29
+ end
30
+
31
+ def mute
32
+ send(type: 'SET_VOLUME', volume: {muted: true})
33
+ end
34
+
35
+ def unmute
36
+ send(type: 'SET_VOLUME', volume: {muted: false})
37
+ end
38
+
39
+ private
40
+
41
+ def next_request_id
42
+ @request_id += 1
43
+ end
44
+
45
+ def send data
46
+ super(data.merge(requestId: next_request_id))
47
+ @request_id
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,87 @@
1
+ require 'rubygems'
2
+ require 'json'
3
+ require 'socket'
4
+ require 'openssl'
5
+
6
+ module Chromecast
7
+ class Connection
8
+ DEFAULT_PORT = 8009
9
+ DEFAULT_SOURCE = 'source-0'
10
+ DEFAULT_DESTINATION = 'receiver-0'
11
+
12
+ attr_reader :host, :port, :source, :destination, :socket
13
+ attr_accessor :key_file, :crt_file
14
+
15
+ def initialize(host)
16
+ @host = host
17
+ @port = DEFAULT_PORT
18
+ @source = DEFAULT_SOURCE
19
+ @destination = DEFAULT_DESTINATION
20
+ @key_file = 'certificate.key'
21
+ @crt_file = 'certificate.crt'
22
+ end
23
+
24
+ def certificate(type)
25
+ case type
26
+ when :crt
27
+ return File.open(crt_file).read
28
+ when :key
29
+ return File.open(key_file).read
30
+ end
31
+ return nil
32
+ end
33
+
34
+ def open
35
+ tcp = TCPSocket.new(host, port)
36
+ ctx = OpenSSL::SSL::SSLContext.new
37
+ ctx.cert = OpenSSL::X509::Certificate.new(certificate(:crt))
38
+ ctx.key = OpenSSL::PKey::RSA.new(certificate(:key))
39
+ ctx.ssl_version = :SSLv23
40
+ ctx.set_params(verify_mode: OpenSSL::SSL::VERIFY_NONE)
41
+
42
+ @socket = OpenSSL::SSL::SSLSocket.new(tcp, ctx).tap do |socket|
43
+ socket.sync_close = true
44
+ socket.connect
45
+ end
46
+
47
+ self
48
+ end
49
+
50
+ def write data
51
+ @socket.write data
52
+ end
53
+
54
+ def has_data?
55
+ ready = IO.select([@socket], nil, nil, 0)
56
+
57
+ return ready != nil
58
+ end
59
+
60
+ def read
61
+ return nil unless has_data?
62
+
63
+ resp_size = @socket.read(4).unpack('N').first
64
+ resp_data = @socket.read(resp_size)
65
+
66
+ resp = Protocol::CastMessage.decode(resp_data)
67
+
68
+ JSON.parse(resp.payload_utf8)
69
+ end
70
+
71
+ def connection
72
+ @connection_channel ||= Channel::Connection.new(self)
73
+ end
74
+
75
+ def receiver
76
+ @receiver_channel ||= Channel::Receiver.new(self)
77
+ end
78
+
79
+ def heartbeat
80
+ @heartbeat_channel ||= Channel::Heartbeat.new(self)
81
+ end
82
+
83
+ def media
84
+ @media_channel ||= Channel::Media.new(self)
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,68 @@
1
+ ##
2
+ # This file is auto-generated. DO NOT EDIT!
3
+ #
4
+ require 'protobuf/message'
5
+
6
+ module Chromecast
7
+ module Protocol
8
+
9
+ ##
10
+ # Message Classes
11
+ #
12
+ class CastMessage < ::Protobuf::Message
13
+ class ProtocolVersion < ::Protobuf::Enum
14
+ define :CASTV2_1_0, 0
15
+ end
16
+
17
+ class PayloadType < ::Protobuf::Enum
18
+ define :STRING, 0
19
+ define :BINARY, 1
20
+ end
21
+
22
+ end
23
+
24
+ class AuthChallenge < ::Protobuf::Message; end
25
+ class AuthResponse < ::Protobuf::Message; end
26
+ class AuthError < ::Protobuf::Message
27
+ class ErrorType < ::Protobuf::Enum
28
+ define :INTERNAL_ERROR, 0
29
+ define :NO_TLS, 1
30
+ end
31
+
32
+ end
33
+
34
+ class DeviceAuthMessage < ::Protobuf::Message; end
35
+
36
+
37
+ ##
38
+ # Message Fields
39
+ #
40
+ class CastMessage
41
+ required ::Chromecast::Protocol::CastMessage::ProtocolVersion, :protocol_version, 1
42
+ required :string, :source_id, 2
43
+ required :string, :destination_id, 3
44
+ required :string, :namespace, 4
45
+ required ::Chromecast::Protocol::CastMessage::PayloadType, :payload_type, 5
46
+ optional :string, :payload_utf8, 6
47
+ optional :bytes, :payload_binary, 7
48
+ end
49
+
50
+ class AuthResponse
51
+ required :bytes, :signature, 1
52
+ required :bytes, :client_auth_certificate, 2
53
+ end
54
+
55
+ class AuthError
56
+ required ::Chromecast::Protocol::AuthError::ErrorType, :error_type, 1
57
+ end
58
+
59
+ class DeviceAuthMessage
60
+ optional ::Chromecast::Protocol::AuthChallenge, :challenge, 1
61
+ optional ::Chromecast::Protocol::AuthResponse, :response, 2
62
+ optional ::Chromecast::Protocol::AuthError, :error, 3
63
+ end
64
+
65
+ end
66
+
67
+ end
68
+
@@ -0,0 +1,24 @@
1
+ module Chromecast
2
+ class Tasks
3
+ include Rake::DSL if defined? Rake::DSL
4
+
5
+ def install_tasks
6
+ desc "Setup all necessary files"
7
+ task setup: ['setup:protocol', 'setup:certificate']
8
+
9
+ namespace :setup do
10
+ desc "Generate certificates for TLS"
11
+ task :certificate do
12
+ system('openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout certificate.key -out certificate.crt')
13
+ end
14
+
15
+ desc "Generate Ruby code for Chromecast protocol"
16
+ task :protocol do
17
+ system('protoc --ruby_out lib/chromecast/ protocol.proto && echo Generated protocol code')
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+
24
+ Chromecast::Tasks.new.install_tasks
@@ -0,0 +1,79 @@
1
+ // Copyright 2013 The Chromium Authors. All rights reserved.
2
+ // Use of this source code is governed by a BSD-style license that can be
3
+ // found in the LICENSE file.
4
+
5
+ syntax = "proto2";
6
+
7
+ option optimize_for = LITE_RUNTIME;
8
+
9
+ package Chromecast.Protocol;
10
+
11
+ message CastMessage {
12
+ // Always pass a version of the protocol for future compatibility
13
+ // requirements.
14
+ enum ProtocolVersion {
15
+ CASTV2_1_0 = 0;
16
+ }
17
+ required ProtocolVersion protocol_version = 1;
18
+
19
+ // source and destination ids identify the origin and destination of the
20
+ // message. They are used to route messages between endpoints that share a
21
+ // device-to-device channel.
22
+ //
23
+ // For messages between applications:
24
+ // - The sender application id is a unique identifier generated on behalf of
25
+ // the sender application.
26
+ // - The receiver id is always the the session id for the application.
27
+ //
28
+ // For messages to or from the sender or receiver platform, the special ids
29
+ // 'sender-0' and 'receiver-0' can be used.
30
+ //
31
+ // For messages intended for all endpoints using a given channel, the
32
+ // wildcard destination_id '*' can be used.
33
+ required string source_id = 2;
34
+ required string destination_id = 3;
35
+
36
+ // This is the core multiplexing key. All messages are sent on a namespace
37
+ // and endpoints sharing a channel listen on one or more namespaces. The
38
+ // namespace defines the protocol and semantics of the message.
39
+ required string namespace = 4;
40
+
41
+ // Encoding and payload info follows.
42
+
43
+ // What type of data do we have in this message.
44
+ enum PayloadType {
45
+ STRING = 0;
46
+ BINARY = 1;
47
+ }
48
+ required PayloadType payload_type = 5;
49
+
50
+ // Depending on payload_type, exactly one of the following optional fields
51
+ // will always be set.
52
+ optional string payload_utf8 = 6;
53
+ optional bytes payload_binary = 7;
54
+ }
55
+
56
+ // Messages for authentication protocol between a sender and a receiver.
57
+ message AuthChallenge {
58
+ }
59
+
60
+ message AuthResponse {
61
+ required bytes signature = 1;
62
+ required bytes client_auth_certificate = 2;
63
+ }
64
+
65
+ message AuthError {
66
+ enum ErrorType {
67
+ INTERNAL_ERROR = 0;
68
+ NO_TLS = 1; // The underlying connection is not TLS
69
+ }
70
+ required ErrorType error_type = 1;
71
+ }
72
+
73
+ message DeviceAuthMessage {
74
+ // Request fields
75
+ optional AuthChallenge challenge = 1;
76
+ // Response fields
77
+ optional AuthResponse response = 2;
78
+ optional AuthError error = 3;
79
+ }
metadata ADDED
@@ -0,0 +1,87 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: chromecast
3
+ version: !ruby/object:Gem::Version
4
+ version: '1.0'
5
+ platform: ruby
6
+ authors:
7
+ - Robert Nasiadek
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-10-04 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: ruby-tls
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: protobuf
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '3.3'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '3.3'
41
+ description: The gem lets you monitor Chromecast status, control volume and launch
42
+ apps.
43
+ email: robert@kapati.net
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - README.rdoc
49
+ - chromecast.gemspec
50
+ - init.rb
51
+ - lib/chromecast.rb
52
+ - lib/chromecast/application.rb
53
+ - lib/chromecast/channel.rb
54
+ - lib/chromecast/channel/base.rb
55
+ - lib/chromecast/channel/connection.rb
56
+ - lib/chromecast/channel/heartbeat.rb
57
+ - lib/chromecast/channel/media.rb
58
+ - lib/chromecast/channel/receiver.rb
59
+ - lib/chromecast/connection.rb
60
+ - lib/chromecast/protocol.pb.rb
61
+ - lib/chromecast/tasks.rb
62
+ - protocol.proto
63
+ homepage: http://robzon.pl/
64
+ licenses:
65
+ - MIT
66
+ metadata: {}
67
+ post_install_message:
68
+ rdoc_options: []
69
+ require_paths:
70
+ - lib
71
+ required_ruby_version: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ required_rubygems_version: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ version: '0'
81
+ requirements: []
82
+ rubyforge_project:
83
+ rubygems_version: 2.2.1
84
+ signing_key:
85
+ specification_version: 4
86
+ summary: Basic Chromecast controll via a Ruby library
87
+ test_files: []