Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Apple Media Service #27

Open
hathach opened this issue Mar 30, 2017 · 2 comments
Open

Support Apple Media Service #27

hathach opened this issue Mar 30, 2017 · 2 comments
Labels
Milestone

Comments

@hathach
Copy link
Member

hathach commented Mar 30, 2017

@hathach hathach modified the milestones: 0.5.5, 0.6 Mar 30, 2017
@hathach hathach added this to TODO in Next Release Apr 8, 2017
@hathach hathach modified the milestones: 0.8, 0.7.0 Jul 12, 2017
@hathach hathach moved this from TODO to Future Release in Next Release Jul 15, 2017
@hathach hathach modified the milestones: 0.8.0, 1.0.0 Jan 5, 2018
@hathach hathach moved this from Future Release to Postponed in Next Release Jan 22, 2019
@Petros144
Copy link

Petros144 commented Jun 5, 2019

` import BLEClient from "bleclient";
import BLEServer from "bleserver";
import {uuid} from "btutils";

const EntityID = {
Player: 0,
Queue: 1,
Track: 2
}
Object.freeze(EntityID);

const EntityUpdateFlags = {
Truncated: (1 << 0),
}
Object.freeze(EntityUpdateFlags);

const TrackAttributeID = {
Artist: 0,
Album: 1,
Title: 2,
Duration: 3
}
Object.freeze(TrackAttributeID);

const PlayerAttributeID = {
Name: 0,
PlaybackInfo: 1,
Volume: 2
}
Object.freeze(TrackAttributeID);

const RemoteCommandID = {
Play: 0,
Pause: 1,
TogglePlayPause: 2,
NextTrack: 3,
PreviousTrack: 4,
VolumeUp: 5,
VolumeDown: 6,
AdvanceRepeatMode: 7,
AdvanceShuffleMode: 8,
SkipForward: 9,
SkipBackward: 10,
LikeTrack: 11,
DislikeTrack: 12,
BookmarkTrack: 13
}
Object.freeze(RemoteCommandID);

const PlaybackState = {
Paused: 0,
Playing: 1,
Rewinding: 2,
FastForwarding: 3,
}
Object.freeze(PlaybackState);

const QueueAttributeID = {
Index: 0,
Count: 1,
ShuffleMode: 2,
RepeatMode: 3,
}
Object.freeze(QueueAttributeID);

const ShuffleMode = {
Off: 0,
One: 1,
All: 2,
}
Object.freeze(ShuffleMode);

const RepeatMode = {
Off: 0,
One: 1,
All: 2,
}
Object.freeze(RepeatMode);

class AMSAuthenticator extends BLEServer {
constructor(client) {
super();
this.client = client;
this.AMS_UUID = uuid89D3502B-0F36-433A-8EF4-C502AD55F8DC;
}
onReady() {
this.deviceName = "Moddable";
this.securityParameters = { mitm:true };
this.onDisconnected();
}
onConnected(device) {
this.device = device;
this.stopAdvertising();
}
onAuthenticated() {
this.client.onAuthenticated(this.device);
}
onDisconnected() {
this.startAdvertising({
advertisingData: {flags: 6, completeName: this.deviceName, solicitationUUID128List: [this.AMS_UUID]}
});
}
}
Object.freeze(AMSAuthenticator.prototype);

class AMSClient extends BLEClient {
constructor(device) {
super();
this.device = device;
this._supportedRemoteCommands = new Uint8Array;
}
onReady() {
this.AMS_UUID = uuid89D3502B-0F36-433A-8EF4-C502AD55F8DC;
this.REMOTE_COMMAND_CHARACTERISTIC_UUID = uuid9B3C81D8-57B1-4A8A-B8DF-0E56F7CA51C2;
this.ENTITY_UPDATE_CHARACTERISTIC_UUID = uuid2F7CABCE-808D-411F-9A0C-BB92BA96C102;
this.ENTITY_ATTRIBUTE_CHARACTERISTIC_UUID = uuidC6B2F38C-23AB-46D8-A6AB-A3A870BBD5D7;
this.securityParameters = { mitm:true };
}
onSecurityParameters() {
this.connect(this.device);
}
onConnected(device) {
device.discoverPrimaryService(this.AMS_UUID);
}
onServices(services) {
if (services.length)
services[0].discoverAllCharacteristics();
}
onCharacteristics(characteristics) {
characteristics.forEach(characteristic => {
let uuid = characteristic.uuid;
if (uuid.equals(this.REMOTE_COMMAND_CHARACTERISTIC_UUID))
this.remoteCommandCharacteristic = characteristic;
else if (uuid.equals(this.ENTITY_UPDATE_CHARACTERISTIC_UUID))
this.entityUpdateCharacteristic = characteristic;
else if (uuid.equals(this.ENTITY_ATTRIBUTE_CHARACTERISTIC_UUID))
this.entityAttributeCharacteristic = characteristic;
});
if (this.remoteCommandCharacteristic)
this.remoteCommandCharacteristic.enableNotifications();
}
onCharacteristicNotificationEnabled(characteristic) {
if (characteristic.uuid.equals(this.REMOTE_COMMAND_CHARACTERISTIC_UUID)) {
if (this.entityUpdateCharacteristic)
this.entityUpdateCharacteristic.enableNotifications();
}
else if (characteristic.uuid.equals(this.ENTITY_UPDATE_CHARACTERISTIC_UUID)) {
let update;
update = Uint8Array.of(EntityID.Track, TrackAttributeID.Artist, TrackAttributeID.Album, TrackAttributeID.Title, TrackAttributeID.Duration);
characteristic.writeWithoutResponse(update.buffer);
update = Uint8Array.of(EntityID.Player, PlayerAttributeID.PlaybackInfo);
characteristic.writeWithoutResponse(update.buffer);
}
}
onCharacteristicNotification(characteristic, buffer) {
if (characteristic.uuid.equals(this.ENTITY_UPDATE_CHARACTERISTIC_UUID)) {
let entityUpdate = new Uint8Array(buffer);
let entityID = entityUpdate[0];
let attributeID = entityUpdate[1];
let flags = entityUpdate[2];
let value = String.fromArrayBuffer(buffer.slice(3));

		if (EntityID.Track == entityID) {
			if (TrackAttributeID.Artist == attributeID)
				this._nextTrack = { artist:value };
			else if (TrackAttributeID.Album == attributeID)
				this._nextTrack.album = value;
			else if (TrackAttributeID.Title == attributeID)
				this._nextTrack.title = value;
			else if (TrackAttributeID.Duration == attributeID)
				this._nextTrack.duration = parseFloat(value);
			if (4 == Object.keys(this._nextTrack).length)
				this.onTrackChanged(this._nextTrack.artist, this._nextTrack.album, this._nextTrack.title, this._nextTrack.duration);
		}
		else if (EntityID.Player == entityID) {
			let parts = value.split(',');
			let playbackState = parseInt(parts[0]);
			let playbackRate = parseFloat(parts[1]);
			let elapsedTime = parseFloat(parts[2]);
			this.onPlaybackInfoChanged(playbackState, playbackRate, elapsedTime);
		}
	}
	else if (characteristic.uuid.equals(this.REMOTE_COMMAND_CHARACTERISTIC_UUID)) {
		this._supportedRemoteCommands = new Uint8Array(buffer);
	}
}
remoteCommand(command) {
	if (this._supportedRemoteCommands.includes(command))
		this.remoteCommandCharacteristic.writeWithoutResponse(Uint8Array.of(command).buffer);
}
onPlaybackInfoChanged(state, rate, elapsed) {
}
onTrackChanged(artist, album, title, duration) {
}

}
Object.freeze(AMSClient.prototype);

export {AMSAuthenticator, AMSClient, RemoteCommandID, PlaybackState, QueueAttributeID, ShuffleMode, RepeatMode};
`

@Petros144
Copy link

someone did this to the NRF52 but with the Nordic SDK

https://github.com/jimmywong2003/nrf5-ancs-ams-wristband-example

maybe this helps for easier Port.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
Next Release
  
Postponed
Development

No branches or pull requests

3 participants