Introduction
Bbox API STB client is a Kotlin HTTP client library for accessing Bbox Miami API. Those API are available if you have a Bbox Miami or Brooklyn which is a Set-Top-Box (TV box) provided by French Internet Service Provider Bouygues Telecom.
This library is fully compatible with Kotlin, Java & Android projects.
Overview
Bbox Miami Api are composed of
REST API used to :
- get TV channel list
- get list of Android applications installed
- get information about a specific installed Android app
- get Android application icon
- get current TV channel
- get volume value
- start an Android app
- display a Toast message
- set volume
Websocket event used to :
- get notified when a TV channel change is detected
- get notified when an Android application state change
- receive custom message from clients
Box IP/port is broadcasted on local network via MDNS (Multicast DNS) on local network
Security
These API are secured using :
- a token which is exchanged for an AppId/AppSecret
- a sessionId which is exchanged for the previous token. This sessionId will be passed in a custom header for each subsequent API call
For more information check API Security section of this page
An AppId & AppSecret are necessary to use this library, you can get them by contacting Bouygues Télécom via this contact page
Usage
Gradle
Dependency is available on JCenter or Maven Central with Gradle, from JCenter or MavenCentral :
repositories {
jcenter() //or mavenCentral()
}
dependencies {
compile 'fr.bmartel:bboxapi-stb:1.1.10' //for JVM
compile 'fr.bmartel:bboxapi-stb-android:1.1.10' //for Android
}
Synchronous & Asynchronous
All methods have an asynchronous & synchronous version. The synchronous version is suffixed with Sync
:
getChannels
is asynchronous, result is returned in callback functiongetChannelsSync
is synchronous, it directly returns the result
Format
The format is the same as the one used in Fuel HTTP client library
- Asynchronous method
The callback function is given in parameter, a Request
& Response
objects are returned along with a Result object holding the data and a FuelError
object in case of failure.
For instance, for getChannels
it will return (Request, Response, Result<List<Channel>, FuelError>)
In case of Java, an Handler
function is used that have 2 callbacks : onSuccess
& onError
- Synchronous method :
A Triple<Request, Response, T>
object is returned.
Service discovery
Desktop platform
val bboxapi = BboxApiStb(appId = "YourAppId", appSecret = "YourAppSecret")
bboxapi.startRestDiscovery(findOneAndExit = true, maxDuration = 10000, platform = DesktopPlatform.create()) { eventType, service, error ->
when (eventType) {
StbServiceEvent.SERVICE_FOUND -> println("service found : ${service?.ip}:${service?.port}")
StbServiceEvent.DISCOVERY_STOPPED -> println("end of discovery")
StbServiceEvent.DISCOVERY_ERROR -> error?.printStackTrace()
}
}
BboxApiStb bboxapi = new BboxApiStb("YourAppId", "YourAppSecret");
bboxapi.startRestDiscovery(true, DesktopPlatform.create(), 10000, (stbServiceEvent, stbService, throwable) -> {
switch (stbServiceEvent) {
case SERVICE_FOUND:
System.out.println("service found : " + stbService.getIp() + ":" + stbService.getPort());
break;
case DISCOVERY_STOPPED:
System.out.println("end of discovery");
break;
case DISCOVERY_ERROR:
throwable.printStackTrace();
break;
}
return Unit.INSTANCE;
});
Android platform
val bboxapi = BboxApiStb(appId = "YourAppId", appSecret = "YourAppSecret")
bboxapi.startRestDiscovery(findOneAndExit = true, maxDuration = 10000, platform = AndroidPlatform.create()) { eventType, service, error ->
when (eventType) {
StbServiceEvent.SERVICE_FOUND -> println("service found : ${service?.ip}:${service?.port}")
StbServiceEvent.DISCOVERY_STOPPED -> println("end of discovery")
StbServiceEvent.DISCOVERY_ERROR -> error?.printStackTrace()
}
}
BboxApiStb bboxapi = new BboxApiStb("YourAppId", "YourAppSecret");
bboxapi.startRestDiscovery(true, AndroidPlatform.create(), 10000, (stbServiceEvent, stbService, throwable) -> {
switch (stbServiceEvent) {
case SERVICE_FOUND:
System.out.println("service found : " + stbService.getIp() + ":" + stbService.getPort());
break;
case DISCOVERY_STOPPED:
System.out.println("end of discovery");
break;
case DISCOVERY_ERROR:
throwable.printStackTrace();
break;
}
return Unit.INSTANCE;
});
In order to find Bbox Miami IP adress, use the mDNS broadcast feature exposed by BboxAPI Miami service
You have to add the following dependency depending on platform :
- Desktop :
implementation "de.mannodermaus.rxjava2:rxbonjour-platform-desktop:2.0.0-RC1"
- Android :
implementation "de.mannodermaus.rxjava2:rxbonjour-platform-android:2.0.0-RC1"
Also using startRestDiscovery
:
- setting
findOneAndExit
totrue
will automatically end the discovery when one service is found - setting
maxDuration
parameter will set a max duration in milliseconds for service discovery
The last service found is automatically chosen and set in variable bboxapi.restService
.
The list of services found is available in bboxapi.restServiceList
. In special environments where there are multiple Bbox Miami or multiple Android TV device with Bbox API STB service installed on the same network, there can be more than one service found if findOneAndExit
is set to false
.
API reference
Display Toast
Asynchronous
val toast = ToastRequest(message = "this is a message", pos_y = 500, pos_x = 300, color = "#FF0000")
bboxapi.displayToast(toast) { request, response, result ->
when (result) {
is Result.Failure -> {
result.getException().printStackTrace()
}
is Result.Success -> {
println(response.statusCode)
}
}
}
ToastRequest toastRequest = new ToastRequest("this is a toast", "#FF0000", 500, 300);
bboxapi.displayToast(toastRequest, new Handler<byte[]>() {
@Override
public void success(Request request, Response response, byte[] bytes) {
System.out.println(response.getStatusCode());
}
@Override
public void failure(Request request, Response response, FuelError fuelError) {
fuelError.printStackTrace();
}
});
Synchronous
val toast = ToastRequest(message = "this is a message", pos_y = 500, pos_x = 200, color = "#FF0000")
val (_, res, result) = bboxapi.displayToastSync(toast)
when (result) {
is Result.Failure -> {
result.getException().printStackTrace()
}
is Result.Success -> {
println(res.statusCode)
}
}
ToastRequest toastRequest = new ToastRequest("this is a toast", "#FF0000", 500, 300);
Triple<Request, Response, Result<byte[], FuelError>> data = bboxapi.displayToastSync(toastRequest);
Request request = data.getFirst();
Response response = data.getSecond();
Result<byte[], FuelError> obj = data.getThird();
System.out.println(response.getStatusCode());
Display a Toast message
Field | Type | default value | Description |
---|---|---|---|
message | string | "" |
toast message |
color | string | null |
toast text color in hex string format (ex: #FF0000) |
pos_x | int | null |
toast X position |
pos_y | int | null |
toast Y position |
Get TV channel list
Asynchronous
bboxapi.getChannels { _, _, result ->
when (result) {
is Result.Failure -> {
result.getException().printStackTrace()
}
is Result.Success -> {
println(result.get())
}
}
}
bboxapi.getChannels(new Handler<List<Channel>>() {
@Override
public void success(Request request, Response response, List<Channel> channels) {
System.out.println(channels);
}
@Override
public void failure(Request request, Response response, FuelError fuelError) {
fuelError.printStackTrace();
}
});
Synchronous
val (_, _, result) = bboxapi.getChannelsSync()
when (result) {
is Result.Failure -> {
result.getException().printStackTrace()
}
is Result.Success -> {
println(result.get())
}
}
Triple<Request, Response, Result<List<Channel>, FuelError>> data = bboxapi.getChannelsSync();
Request request = data.getFirst();
Response response = data.getSecond();
Result<List<Channel>, FuelError> obj = data.getThird();
System.out.println(obj.get());
Get list of TV channel with the following information :
Field | Type | Description |
---|---|---|
mediaState | string | state of media play or stop |
mediaTitle | string | channel name |
positionId | int | channel position |
Get Application list
Asynchronous
bboxapi.getApps { _, _, result ->
when (result) {
is Result.Failure -> {
result.getException().printStackTrace()
}
is Result.Success -> {
println(result.get())
}
}
}
bboxapi.getApps(new Handler<List<Application>>() {
@Override
public void success(Request request, Response response, List<Application> channels) {
System.out.println(channels);
}
@Override
public void failure(Request request, Response response, FuelError fuelError) {
fuelError.printStackTrace();
}
});
Synchronous
val (_, _, result) = bboxapi.getAppsSync()
when (result) {
is Result.Failure -> {
result.getException().printStackTrace()
}
is Result.Success -> {
println(result.get())
}
}
Triple<Request, Response, Result<List<Application>, FuelError>> data = bboxapi.getAppsSync();
Request request = data.getFirst();
Response response = data.getSecond();
Result<List<Application>, FuelError> obj = data.getThird();
System.out.println(obj.get());
Get list of Android application installed on Bbox. The information includes the following :
Field | Type | Description |
---|---|---|
appId | string | application id |
appName | string | application name (ex: Youtube) |
packageName | string | application package name (ex: com.google.youtube) |
component | string | component intent |
appState | string | application state (stopped/foreground) |
data | string | data intent |
leanback | boolean | if app is an Android TV app |
logoUrl | string | path to getAppIcon for this package name |
Get Application info
Asynchronous
bboxapi.getAppInfo(packageName = "com.google.android.youtube.tv") { _, _, result ->
when (result) {
is Result.Failure -> {
val ex = result.getException()
ex.printStackTrace()
}
is Result.Success -> {
val data = result.get()
println(data)
}
}
}
bboxapi.getAppInfo("com.google.android.youtube.tv", new Handler<List<Application>>() {
@Override
public void success(Request request, Response response, List<Application> channels) {
System.out.println(channels);
}
@Override
public void failure(Request request, Response response, FuelError fuelError) {
fuelError.printStackTrace();
}
});
Synchronous
val (_, _, result) = bboxapi.getAppInfoSync(packageName = "com.google.android.youtube.tv")
when (result) {
is Result.Failure -> {
result.getException().printStackTrace()
}
is Result.Success -> {
println(result.get())
}
}
Triple<Request, Response, Result<List<Application>, FuelError>> data = bboxapi.getAppInfoSync("com.google.android.youtube.tv");
Request request = data.getFirst();
Response response = data.getSecond();
Result<List<Application>, FuelError> obj = data.getThird();
System.out.println(obj.get());
Get information about a specific application by package name :
Field | Type | Description |
---|---|---|
appId | string | application id |
appName | string | application name (ex: Youtube) |
packageName | string | application package name (ex: com.google.youtube) |
component | string | component intent |
appState | string | application state (stopped/foreground) |
data | string | data intent |
leanback | boolean | if app is an Android TV app |
logoUrl | string | path to getAppIcon for this package name |
Get Application icon
Asynchronous
bboxapi.getAppIcon(packageName = "com.google.android.youtube.tv") { _, _, result ->
when (result) {
is Result.Failure -> {
result.getException().printStackTrace()
}
is Result.Success -> {
println(result.get().size)
}
}
}
bboxapi.getAppIcon("com.google.android.youtube.tv", new Handler<byte[]>() {
@Override
public void success(Request request, Response response, byte[] image) {
System.out.println(image.length);
}
@Override
public void failure(Request request, Response response, FuelError fuelError) {
fuelError.printStackTrace();
}
});
Synchronous
val (_, _, result) = bboxapi.getAppIconSync(packageName = "com.google.android.youtube.tv")
when (result) {
is Result.Failure -> {
result.getException().printStackTrace()
}
is Result.Success -> {
println(result.get().size)
}
}
Triple<Request, Response, Result<byte[], FuelError>> data = bboxapi.getAppIconSync("com.google.android.youtube.tv");
Request request = data.getFirst();
Response response = data.getSecond();
Result<byte[], FuelError> obj = data.getThird();
System.out.println(obj.get().length);
Retrieve Android application icon for a specific package name
Get current TV channel
Asynchronous
bboxapi.getCurrentChannel { _, _, result ->
when (result) {
is Result.Failure -> {
result.getException().printStackTrace()
}
is Result.Success -> {
println(result.get())
}
}
}
bboxapi.getCurrentChannel(new Handler<Media>() {
@Override
public void success(Request request, Response response, Media channel) {
System.out.println(channel);
}
@Override
public void failure(Request request, Response response, FuelError fuelError) {
fuelError.printStackTrace();
}
});
Synchronous
val (_, _, result) = bboxapi.getCurrentChannelSync()
when (result) {
is Result.Failure -> {
result.getException().printStackTrace()
}
is Result.Success -> {
println(result.get())
}
}
Triple<Request, Response, Result<Media, FuelError>> data = bboxapi.getCurrentChannelSync();
Request request = data.getFirst();
Response response = data.getSecond();
Result<Media, FuelError> obj = data.getThird();
System.out.println(obj.get());
Get current TV channel with the following information :
Field | Type | Description |
---|---|---|
mediaService | string | |
mediaState | string | media state (stop/play) |
mediaTitle | string | channel name |
Get volume
Asynchronous
bboxapi.getVolume { _, _, result ->
when (result) {
is Result.Failure -> {
result.getException().printStackTrace()
}
is Result.Success -> {
println(result.get())
}
}
}
bboxapi.getVolume(new Handler<Volume>() {
@Override
public void success(Request request, Response response, Volume volume) {
System.out.println(volume);
}
@Override
public void failure(Request request, Response response, FuelError fuelError) {
fuelError.printStackTrace();
}
});
Synchronous
val (_, _, result) = bboxapi.getVolumeSync()
when (result) {
is Result.Failure -> {
result.getException().printStackTrace()
}
is Result.Success -> {
println(result.get())
}
}
Triple<Request, Response, Result<Volume, FuelError>> data = bboxapi.getVolumeSync();
Request request = data.getFirst();
Response response = data.getSecond();
Result<Volume, FuelError> obj = data.getThird();
System.out.println(obj.get());
Get volume value
Get current TV channel with the following information :
Field | Type | Description |
---|---|---|
volume | string | volume value (yes this is a string !?) |
Set volume
Asynchronous
bboxapi.setVolume(volume = 10) { _, response, result ->
when (result) {
is Result.Failure -> {
result.getException().printStackTrace()
}
is Result.Success -> {
println(response.statusCode)
}
}
}
bboxapi.setVolume(10, new Handler<byte[]>() {
@Override
public void success(Request request, Response response, byte[] body) {
System.out.println(response.getStatusCode());
}
@Override
public void failure(Request request, Response response, FuelError fuelError) {
fuelError.printStackTrace();
}
});
Synchronous
val (_, response, result) = bboxapi.setVolumeSync(volume = 100)
when (result) {
is Result.Failure -> {
result.getException().printStackTrace()
}
is Result.Success -> {
println(response.statusCode)
}
}
Triple<Request, Response, Result<byte[], FuelError>> data = bboxapi.setVolumeSync(100);
Request request = data.getFirst();
Response response = data.getSecond();
Result<byte[], FuelError> obj = data.getThird();
System.out.println(response.getStatusCode());
Set volume
Field | Type | Description |
---|---|---|
volume | int | volume value |
Start application
Asynchronous
bboxapi.startApp(packageName = "com.google.android.youtube.tv") { _, response, result ->
when (result) {
is Result.Failure -> {
result.getException().printStackTrace()
}
is Result.Success -> {
println(response.statusCode)
}
}
}
bboxapi.startApp("com.google.android.youtube.tv", new Handler<byte[]>() {
@Override
public void success(Request request, Response response, byte[] body) {
System.out.println(response.getStatusCode());
}
@Override
public void failure(Request request, Response response, FuelError fuelError) {
fuelError.printStackTrace();
}
});
Synchronous
val (_, response, result) = bboxapi.startAppSync(packageName = "com.google.android.youtube.tv")
when (result) {
is Result.Failure -> {
result.getException().printStackTrace()
}
is Result.Success -> {
println(response.statusCode)
}
}
Triple<Request, Response, Result<byte[], FuelError>> data = bboxapi.startAppSync("com.google.android.youtube.tv");
Request request = data.getFirst();
Response response = data.getSecond();
Result<byte[], FuelError> obj = data.getThird();
System.out.println(response.getStatusCode());
Start Android application by package name
Field | Type | Description |
---|---|---|
packageName | string | application package name (ex: com.google.youtube) |
Custom HTTP request
Asynchronous
bboxapi.createCustomRequest(bboxapi.manager.request(method = Method.GET, path = "/applications")) { _, _, result ->
when (result) {
is Result.Failure -> {
result.getException().printStackTrace()
}
is Result.Success -> {
println(String(result.get()))
}
}
}
bboxapi.createCustomRequest(bboxapi.getManager().request(Method.GET, "/applications", null), new Handler<byte[]>() {
@Override
public void success(Request request, Response response, byte[] data) {
System.out.println(new String(data));
}
@Override
public void failure(Request request, Response response, FuelError fuelError) {
fuelError.printStackTrace();
}
});
Synchronous
val (_, _, result) = bboxapi.createCustomRequestSync(bboxapi.manager.request(method = Method.GET, path = "/applications"))
when (result) {
is Result.Failure -> {
result.getException().printStackTrace()
}
is Result.Success -> {
println(String(result.get()))
}
}
Triple<Request, Response, Result<byte[], FuelError>> data = bboxapi.createCustomRequestSync(bboxapi.getManager().request(Method.GET, "/applications", null));
Request request = data.getFirst();
Response response = data.getSecond();
Result<byte[], FuelError> obj = data.getThird();
System.out.println(new String(obj.get()));
Create your own HTTP request, this can be useful for not relying on the library implementation
All request construction are prefixed with http://$boxIp:$boxRestPort/api.bbox.lan/v0 if host is not specified
Notifications
BboxAPI Miami service dispatch notifications via WebSocket on port 9090. This library abstracts all the flow involving app registering, subscribing to events & opening websocket. This flow is described here
Subscribe notifications
val notificationChannel = bboxapi.subscribeNotification(
appName = "myApplication",
resourceList = listOf(Resource.Application, Resource.Media, Resource.Message),
listener = object : BboxApiStb.WebSocketListener {
override fun onOpen() {
println("websocket opened")
}
override fun onClose() {
println("websocket closed")
}
override fun onApp(app: AppEvent) {
println("application event : $app")
}
override fun onMedia(media: MediaEvent) {
println("channel change event : $media")
}
override fun onMessage(message: MessageEvent) {
println("message event : $message")
}
override fun onError(error: BboxApiError) {
println("error : $error")
}
override fun onFailure(throwable: Throwable?) {
throwable?.printStackTrace()
}
})
val (_, _, result) = notificationChannel.subscribeResult
if (result is Result.Failure) {
result.error.printStackTrace()
} else {
println("subscribed on channelId ${notificationChannel.channelId} & appId ${notificationChannel.appId}")
}
List<Resource> resourceList = new ArrayList<>();
resourceList.add(Resource.Application);
resourceList.add(Resource.Media);
resourceList.add(Resource.Message);
NotificationChannel notificationChannel = bboxapi.subscribeNotification(
"myApplication",
resourceList,
new BboxApiStb.WebSocketListener() {
@Override
public void onOpen() {
System.out.println("websocket opened");
}
@Override
public void onClose() {
System.out.println("websocket closed");
}
@Override
public void onError(@NotNull BboxApiError error) {
System.out.println("error : " + error);
}
@Override
public void onMedia(@NotNull MediaEvent media) {
System.out.println("channel change event : " + media);
}
@Override
public void onApp(@NotNull AppEvent app) {
System.out.println("application event : " + app);
}
@Override
public void onMessage(@NotNull MessageEvent message) {
System.out.println("message event : " + message);
}
@Override
public void onFailure(@Nullable Throwable throwable) {
throwable.printStackTrace();
}
});
Triple<Request, Response, Result<byte[], FuelError>> result = notificationChannel.getSubscribeResult();
if (result.component3().component2() != null) {
result.component3().component2().printStackTrace();
} else {
System.out.println("subscribed with resource on channelId " +
notificationChannel.getChannelId() +
" & appId " + notificationChannel.getAppId());
}
To listen for notifications use subscribeNotification
with a list of Resource
including the following :
Resource.Application
to receive application state change (when an application is going background/foreground)Resource.Media
to receive TV channel change eventsResource.Message
to receive messages sent by other BboxAPI STB client
Field | Type | Description |
---|---|---|
appName | string | a name for your application |
resourceList | List |
list of resources to subscribe |
listener | BboxApiStb.WebSocketListener | websocket event listener |
The underlying flow registers an "application" & subscribes notifications, an appID/channelID is returned from :
appID
is the ID returned by registering an "application" for notificationschannelID
is the ID retuned by subscribing the previous "application" for specific resources
These appID/channelID can be used to send notification, check "Send notification" section
When you are done listening to notification call closeWebsocket
to close the websocket.
Unsubscribe all channels
To unsubscribe all channels ID call unsubscribeAllSync
. This will get all distinct opened channels & unsubscribe each of them
bboxapi.unsubscribeAllSync()
bboxapi.unsubscribeAllSync();
Send notification
Asynchronous
bboxapi.sendNotification(
channelId = "15233003603520.7238189308864336-0.0718767910445014",
appId = "15233003603520.7238189308864336",
message = "some message") { _, response, result ->
when (result) {
is Result.Failure -> {
result.getException().printStackTrace()
}
is Result.Success -> {
println("message sent")
}
}
}
bboxapi.sendNotification(
"15233003603520.7238189308864336-0.0718767910445014",
"15233003603520.7238189308864336",
"some message", new Handler<byte[]>() {
@Override
public void success(Request request, Response response, byte[] bytes) {
System.out.println("message sent");
}
@Override
public void failure(Request request, Response response, FuelError fuelError) {
fuelError.printStackTrace();
}
}
);
Synchronous
val (_, _, result) = bboxapi.sendNotificationSync(
channelId = "15233003603520.7238189308864336-0.0718767910445014",
appId = "15233003603520.7238189308864336",
message = "some message")
when (result) {
is Result.Failure -> {
result.getException().printStackTrace()
}
is Result.Success -> {
println("message sent")
}
}
Triple<Request, Response, Result<byte[], FuelError>> result = bboxapi.sendNotificationSync(
"15233003603520.7238189308864336-0.0718767910445014",
"15233003603520.7238189308864336",
"some message"
);
if (result.component3().component2() != null) {
result.component3().component2().printStackTrace();
} else {
System.out.println("message sent");
}
You can send a notification to a pair channelId / appId.
Field | Type | Description |
---|---|---|
channelId | string | ID retuned by subscribing the previous "application" for specific resources |
appId | string | ID returned by registering an "application" for notifications |
message | string | message to send |
Errors
check exception type & response status code on failure :
bboxapi.getApps { _, response, result ->
when (result) {
is Result.Failure -> {
val ex = result.getException()
when {
ex.exception is HttpException -> println("http error : ${response.statusCode}")
else -> ex.printStackTrace()
}
}
is Result.Success -> {
val data = result.get()
println(data)
}
}
}
bboxapi.getApps(new Handler<List<Application>>() {
@Override
public void failure(Request request, Response response, FuelError error) {
if (error.getException() instanceof HttpException) {
System.out.println("http error : " + response.getStatusCode());
} else {
error.printStackTrace();
}
}
@Override
public void success(Request request, Response response, List<Application> data) {
System.out.println(data);
}
});
FuelError
can be checked for exceptions, for instance :
Exception | description |
---|---|
HttpException | a non 2XX HTTP response was received, check the status code from the response |
Among HttpException
, you can find the following :
Error Code | Meaning |
---|---|
400 | Bad Request -- request format is invalid |
404 | Not Found -- endpoint doesn't exist (check it starts with http://$IP:$PORT/api.bbox.lan/v0) |