# Intégration mobile | Docs AudioDN

> Intégrez AudioDN dans votre application iOS, Android ou Flutter à l

Source: https://audiodeliverynetwork.com/fr/docs/integration/mobile/

---

# Intégration mobile

Utilisez l’API ADN avec des clés API côté client depuis votre application iOS, Android ou Flutter. Lisez et envoyez avec des éléments média natifs.

#### Idéal pour

Applications iOS, Android et Flutter. La configuration prend environ 30 minutes.

## Lecture des pistes

Créez des sessions de lecture et streamez l’audio avec des lecteurs média natifs.

Appelez l’API de lecture ADN directement depuis votre application mobile à l’aide d’une clé API côté client. Créez des sessions de lecture, récupérez des URL de variante signées, et streamez l’audio avec des lecteurs média natifs.

### 1\. Créer une clé API Client-Side Player

Sous **Settings → API Keys**, créez une clé **Client-Side Player**. Limitez-la aux variantes et au contenu auxquels votre application mobile doit accéder.

### 2\. Créer une session de lecture

  

```
import Foundation

struct PlaySessionRequest: Encodable {
    let collectionId: String
    let variants: [String]
    let isDownloadable: Bool
    let expiresIn: Int
    enum CodingKeys: String, CodingKey {
        case collectionId = "collection_id"; case variants
        case isDownloadable = "is_downloadable"; case expiresIn = "expires_in"
    }
}
struct PlaySessionResponse: Decodable {
    let playSessionId: String
    let tracks: [Track]
    enum CodingKeys: String, CodingKey { case playSessionId = "play_session_id"; case tracks }
    struct Track: Decodable { let id: String }
}

func createPlaySession() async throws -> (playSessionId: String, tracks: [PlaySessionResponse.Track]) {
    var request = URLRequest(url: URL(string: "https://api.audiodelivery.net/v1/play_session/collection")!)
    request.httpMethod = "POST"
    request.setValue("Bearer CLIENT-SIDE-PLAYER-API-KEY", forHTTPHeaderField: "Authorization")
    request.setValue("application/json", forHTTPHeaderField: "Content-Type")
    let encoder = JSONEncoder()
    encoder.keyEncodingStrategy = .convertToSnakeCase
    request.httpBody = try encoder.encode(PlaySessionRequest(
        collectionId: "COLLECTION-ID", variants: ["hq", "lq"], isDownloadable: false, expiresIn: 3600))
    let (data, _) = try await URLSession.shared.data(for: request)
    let decoder = JSONDecoder()
    decoder.keyDecodingStrategy = .convertFromSnakeCase
    let session = try decoder.decode(PlaySessionResponse.self, from: data)
    return (session.playSessionId, session.tracks)
}
```

```
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import okhttp3.*
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.RequestBody.Companion.toRequestBody

@Serializable
data class PlaySessionRequest(val collection_id: String, val variants: List<String>,
    val is_downloadable: Boolean, val expires_in: Int)
@Serializable
data class PlaySessionResponse(val play_session_id: String, val tracks: List<PlaySessionTrack>) {
    @Serializable data class PlaySessionTrack(val id: String)
}

val client = OkHttpClient()
val json = Json { ignoreUnknownKeys = true }

suspend fun createPlaySession(): PlaySessionResponse = withContext(Dispatchers.IO) {
    val body = json.encodeToString(PlaySessionRequest.serializer(), PlaySessionRequest(
        "COLLECTION-ID", listOf("hq", "lq"), false, 3600)).toRequestBody("application/json".toMediaType())
    val request = Request.Builder()
        .url("https://api.audiodelivery.net/v1/play_session/collection")
        .post(body)
        .addHeader("Authorization", "Bearer CLIENT-SIDE-PLAYER-API-KEY")
        .build()
    val response = client.newCall(request).execute()
    val responseBody = response.body?.string() ?: error("Empty response body")
    json.decodeFromString<PlaySessionResponse>(responseBody)
}
```

```
import 'dart:convert';
import 'package:http/http.dart' as http;

Future<({String playSessionId, List<dynamic> tracks})> createPlaySession() async {
  final response = await http.post(
    Uri.parse('https://api.audiodelivery.net/v1/play_session/collection'),
    headers: {
      'Authorization': 'Bearer CLIENT-SIDE-PLAYER-API-KEY',
      'Content-Type': 'application/json',
    },
    body: jsonEncode({
      'collection_id': 'COLLECTION-ID',
      'variants': ['hq', 'lq'],
      'is_downloadable': false,
      'expires_in': 3600,
    }),
  );
  if (response.statusCode < 200 || response.statusCode >= 300) {
    throw Exception('Play session failed: ${response.statusCode}');
  }
  final json = jsonDecode(response.body) as Map<String, dynamic>;
  return (
    playSessionId: json['play_session_id'] as String,
    tracks: json['tracks'] as List<dynamic>,
  );
}
```

### 3\. Récupérer les URL de variantes de piste

  

```
struct TrackVariantsResponse: Decodable {
    let variants: [Variant]
    struct Variant: Decodable {
        let url: String
        let variant: VariantIndex
        struct VariantIndex: Decodable { let id: String }
    }
}

// No auth needed — the session ID acts as a bearer token
func getAudioUrl(playSessionId: String, trackId: String) async throws -> String {
    let url = URL(string: "https://api.audiodelivery.net/v1/play/\(playSessionId)/\(trackId)")!
    let (data, _) = try await URLSession.shared.data(from: url)
    let decoder = JSONDecoder()
    decoder.keyDecodingStrategy = .convertFromSnakeCase
    let res = try decoder.decode(TrackVariantsResponse.self, from: data)
    let hq = res.variants.first { $0.variant.id == "hq" }!
    return hq.url
}
```

```
@Serializable
data class TrackVariantsResponse(val variants: List<Variant>) {
    @Serializable data class Variant(val url: String, val variant: VariantIndex) {
        @Serializable data class VariantIndex(val id: String)
    }
}

// No auth needed — the session ID acts as a bearer token
suspend fun getAudioUrl(playSessionId: String, trackId: String): String = withContext(Dispatchers.IO) {
    val request = Request.Builder()
        .url("https://api.audiodelivery.net/v1/play/$playSessionId/$trackId")
        .get()
        .build()
    val response = client.newCall(request).execute()
    val responseBody = response.body?.string() ?: error("Empty response body")
    val res = json.decodeFromString<TrackVariantsResponse>(responseBody)
    res.variants.first { it.variant.id == "hq" }.url
}
```

```
import 'dart:convert';
import 'package:http/http.dart' as http;

// No auth needed — the session ID acts as a bearer token
Future<String> getAudioUrl(String playSessionId, String trackId) async {
  final response = await http.get(
    Uri.parse('https://api.audiodelivery.net/v1/play/$playSessionId/$trackId'),
  );
  if (response.statusCode < 200 || response.statusCode >= 300) {
    throw Exception('Fetch track failed: ${response.statusCode}');
  }
  final json = jsonDecode(response.body) as Map<String, dynamic>;
  final variants = json['variants'] as List<dynamic>;
  final hqVariant = variants.cast<Map<String, dynamic>>().firstWhere(
    (v) => (v['variant'] as Map<String, dynamic>)['id'] == 'hq',
    orElse: () => throw StateError('HQ variant not found'),
  );
  return hqVariant['url'] as String;
}
```

### 4\. Lire avec le lecteur natif

  

```
import AVFoundation

let player = AVPlayer(url: URL(string: audioUrl)!)
player.play()
```

```
import androidx.media3.common.MediaItem
import androidx.media3.exoplayer.ExoPlayer

val player = ExoPlayer.Builder(context).build()
val mediaItem = MediaItem.fromUri(audioUrl)
player.setMediaItem(mediaItem)
player.prepare()
player.play()
```

```
import 'package:just_audio/just_audio.dart';

final player = AudioPlayer();
await player.setUrl(audioUrl);
await player.play();
// Dispose when done: player.dispose();
```

L’URL signée fonctionne avec n’importe quel lecteur média natif. ADN gère la livraison CDN et la vérification d’URL.

#### Données de forme d'onde

ADN génère automatiquement des données de forme d’onde RMS normalisées (320 échantillons) pour chaque piste. Le champ `levels` dans la réponse de la piste contient les données — utilisez-les pour afficher des formes d’onde dans votre propre lecteur, ou générez des formes d’onde personnalisées via le type de variante Analyse audio.

#### Images de couverture et couleurs de thème

ADN scanne chaque piste envoyée pour une pochette intégrée et la rend disponible comme `cover_image` dans la réponse de la piste (en tailles icon/small/regular/large). Vous pouvez aussi envoyer une image de couverture séparément via l’API. Lorsqu’une image de couverture est présente, ADN extrait une palette de couleurs et sélectionne un `player_color` principal — utilisez-les pour styliser l’interface de votre lecteur. À côté, les réponses incluent `player_color_light` et `player_color_dark`, des variantes ajustées en contraste pour afficher la couleur sur des fonds clairs et sombres respectivement. Le tableau `theme` complet contient toutes les couleurs extraites avec les valeurs hexadécimales, l’aire, la luminosité et les données de saturation.

## Envoi des pistes

Envoyez des fichiers audio depuis des applications natives à l’aide de l’API d’envoi ADN.

Appelez l’API d’envoi ADN directement depuis votre application mobile à l’aide d’une clé API côté client. Créez des sessions d’envoi, obtenez des URL pré-signées, et envoyez des fichiers audio — le tout depuis du code natif.

### 1\. Créer une clé API Client-Side Upload

Connectez-vous à ADN, allez dans **Settings → API Keys**, et créez une clé **Client-Side Upload**. Limitez-la à la collection vers laquelle votre application doit envoyer.

### 2\. Créer une session d'envoi

  

```
import Foundation

struct UploadSessionRequest: Encodable {
    let collectionId: String
    let expiresIn: Int
    enum CodingKeys: String, CodingKey { case collectionId = "collection_id"; case expiresIn = "expires_in" }
}
struct UploadSessionResponse: Decodable {
    let uploadSession: UploadSession
    enum CodingKeys: String, CodingKey { case uploadSession = "upload_session" }
    struct UploadSession: Decodable { let id: String }
}

func createUploadSession() async throws -> String {
    var request = URLRequest(url: URL(string: "https://api.audiodelivery.net/v1/upload_session")!)
    request.httpMethod = "POST"
    request.setValue("Bearer CLIENT-SIDE-UPLOAD-API-KEY", forHTTPHeaderField: "Authorization")
    request.setValue("application/json", forHTTPHeaderField: "Content-Type")
    let encoder = JSONEncoder()
    encoder.keyEncodingStrategy = .convertToSnakeCase
    request.httpBody = try encoder.encode(UploadSessionRequest(collectionId: "COLLECTION-ID", expiresIn: 3600))
    let (data, _) = try await URLSession.shared.data(for: request)
    let decoder = JSONDecoder()
    decoder.keyDecodingStrategy = .convertFromSnakeCase
    let session = try decoder.decode(UploadSessionResponse.self, from: data)
    return session.uploadSession.id
}
```

```
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import okhttp3.*
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.RequestBody.Companion.toRequestBody

@Serializable
data class UploadSessionRequest(val collection_id: String, val expires_in: Int)
@Serializable
data class UploadSessionResponse(val upload_session: UploadSession) {
    @Serializable data class UploadSession(val id: String)
}

val client = OkHttpClient()
val json = Json { ignoreUnknownKeys = true }

suspend fun createUploadSession(): String = withContext(Dispatchers.IO) {
    val body = json.encodeToString(UploadSessionRequest.serializer(),
        UploadSessionRequest("COLLECTION-ID", 3600)).toRequestBody("application/json".toMediaType())
    val request = Request.Builder()
        .url("https://api.audiodelivery.net/v1/upload_session")
        .post(body)
        .addHeader("Authorization", "Bearer CLIENT-SIDE-UPLOAD-API-KEY")
        .build()
    val response = client.newCall(request).execute()
    val responseBody = response.body?.string() ?: error("Empty response body")
    val session = json.decodeFromString<UploadSessionResponse>(responseBody)
    session.upload_session.id
}
```

```
import 'dart:convert';
import 'package:http/http.dart' as http;

Future<String> createUploadSession() async {
  final response = await http.post(
    Uri.parse('https://api.audiodelivery.net/v1/upload_session'),
    headers: {
      'Authorization': 'Bearer CLIENT-SIDE-UPLOAD-API-KEY',
      'Content-Type': 'application/json',
    },
    body: jsonEncode({
      'collection_id': 'COLLECTION-ID',
      'expires_in': 3600,
    }),
  );
  if (response.statusCode < 200 || response.statusCode >= 300) {
    throw Exception('Upload session failed: ${response.statusCode}');
  }
  final json = jsonDecode(response.body) as Map<String, dynamic>;
  return (json['upload_session'] as Map<String, dynamic>)['id'] as String;
}
```

### 3\. Obtenir une URL d'envoi de piste

  

```
struct TrackUploadRequest: Encodable {
    let fileName: String
    enum CodingKeys: String, CodingKey { case fileName = "file_name" }
}
struct TrackUploadResponse: Decodable {
    let trackUpload: TrackUpload
    enum CodingKeys: String, CodingKey { case trackUpload = "track_upload" }
    struct TrackUpload: Decodable { let uploadUrl: String; let method: String
        enum CodingKeys: String, CodingKey { case uploadUrl = "upload_url"; case method }
    }
}

func getTrackUploadUrl(sessionId: String) async throws -> (url: String, method: String) {
    var request = URLRequest(url: URL(string: "https://api.audiodelivery.net/v1/upload/\(sessionId)/track")!)
    request.httpMethod = "POST"
    request.setValue("application/json", forHTTPHeaderField: "Content-Type")
    let encoder = JSONEncoder()
    encoder.keyEncodingStrategy = .convertToSnakeCase
    request.httpBody = try encoder.encode(TrackUploadRequest(fileName: "recording.m4a"))
    let (data, _) = try await URLSession.shared.data(for: request)
    let decoder = JSONDecoder()
    decoder.keyDecodingStrategy = .convertFromSnakeCase
    let res = try decoder.decode(TrackUploadResponse.self, from: data)
    return (res.trackUpload.uploadUrl, res.trackUpload.method)
}
```

```
@Serializable
data class TrackUploadRequest(val file_name: String)
@Serializable
data class TrackUploadResponse(val track_upload: TrackUpload) {
    @Serializable data class TrackUpload(val upload_url: String, val method: String)
}

suspend fun getTrackUploadUrl(uploadSessionId: String): Pair<String, String> = withContext(Dispatchers.IO) {
    val body = json.encodeToString(TrackUploadRequest.serializer(), TrackUploadRequest("recording.m4a"))
        .toRequestBody("application/json".toMediaType())
    val request = Request.Builder()
        .url("https://api.audiodelivery.net/v1/upload/$uploadSessionId/track")
        .post(body)
        .build()
    val response = client.newCall(request).execute()
    val responseBody = response.body?.string() ?: error("Empty response body")
    val res = json.decodeFromString<TrackUploadResponse>(responseBody)
    res.track_upload.upload_url to res.track_upload.method
}
```

```
import 'dart:convert';
import 'package:http/http.dart' as http;

Future<(String, String)> getTrackUploadUrl(String uploadSessionId) async {
  final response = await http.post(
    Uri.parse('https://api.audiodelivery.net/v1/upload/$uploadSessionId/track'),
    headers: {'Content-Type': 'application/json'},
    body: jsonEncode({'file_name': 'recording.m4a'}),
  );
  if (response.statusCode < 200 || response.statusCode >= 300) {
    throw Exception('Track upload URL failed: ${response.statusCode}');
  }
  final json = jsonDecode(response.body) as Map<String, dynamic>;
  final trackUpload = json['track_upload'] as Map<String, dynamic>;
  return (trackUpload['upload_url'] as String, trackUpload['method'] as String);
}
```

### 4\. Envoyer le fichier

  

```
// Prefer streaming for large files instead of loading into memory
func uploadAudio(fileURL: URL, uploadURL: URL, method: String) async throws {
    var request = URLRequest(url: uploadURL)
    request.httpMethod = method
    request.setValue("audio/mp4", forHTTPHeaderField: "Content-Type")
    request.httpBodyStream = InputStream(url: fileURL)
    let (_, uploadResponse) = try await URLSession.shared.data(for: request)
    let httpResponse = uploadResponse as! HTTPURLResponse
    print("Upload status: \(httpResponse.statusCode)") // 200 on success
}
```

```
import okhttp3.RequestBody.Companion.asRequestBody
import java.io.File

suspend fun uploadFile(uploadUrl: String, filePath: String): Int = withContext(Dispatchers.IO) {
    val file = File(filePath)
    val fileBody = file.asRequestBody("audio/mp4".toMediaType())
    val request = Request.Builder()
        .url(uploadUrl)
        .put(fileBody)
        .addHeader("Content-Type", "audio/mp4")
        .build()
    val response = client.newCall(request).execute()
    response.code
}
```

```
import 'dart:io';
import 'package:http/http.dart' as http;

// For large files, use body: File(path).openRead() to stream instead of loading into memory
Future<void> uploadFile(String uploadUrl, String filePath) async {
  final file = File(filePath);
  final response = await http.put(
    Uri.parse(uploadUrl),
    headers: {'Content-Type': 'audio/mp4'},
    body: await file.readAsBytes(),
  );
  if (response.statusCode < 200 || response.statusCode >= 300) {
    throw Exception('Upload failed: ${response.statusCode}');
  }
}
```

Répétez les étapes 3 et 4 pour chaque fichier — chaque piste a besoin de sa propre requête d’envoi et URL signée. Vous pouvez aussi inclure un objet `track` imbriqué à la création de session pour créer la première piste d’emblée, puis utiliser l’étape 3 pour tout fichier supplémentaire.

### 5\. Attendre que la piste soit prête

Le traitement commence automatiquement après l’envoi. Avant de lire la piste, attendez qu’elle soit prête — interrogez `GET /v1/track/{track_id}` jusqu’à ce que `track.track_status_id` soit `ready`, ou configurez un webhook (étape suivante) pour être notifié au lieu d’interroger.

### 6\. Optionnel : Webhooks

Ajoutez un webhook sous **Settings → Webhook** pour être notifié lorsqu’une piste atteint un résultat terminal (`ready`, `incomplete`, `error` ou `init_error`) et lorsque son jeu de fichiers complet est prêt. Voir la documentation du [webhook Track Processing](/docs/webhooks/track-processing).

#### Images de couverture

ADN scanne automatiquement les pistes envoyées pour une pochette intégrée. Vous pouvez aussi envoyer une image de couverture séparément via l’API Covers. Lorsqu’une image est présente, ADN extrait des couleurs (disponibles comme `theme`) qui peuvent être utilisées pour styliser l’interface de votre lecteur.
