-
Notifications
You must be signed in to change notification settings - Fork 25
Attaching media to a Tweet
Roberto Doering edited this page Jun 12, 2020
·
4 revisions
To attach media (images / GIFs / videos) to a Tweet, we first have to upload the media independently and then include the returned mediaId
in the statuses/update
request (twitterApi.tweetService.update(status: '...')
).
- This package supports the chunked media upload.
- A chunk is <= 5 MB in size
- A tweet may contain up to 4 images, 1 animated GIF or 1 video.
- Images must be <= 5 MB; GIFs <= 15 MB; videos <= 512 MB
More specifications and recommendations are listed here.
- The upload is initialized using
twitterApi.mediaService.uploadInit(...)
. - Each chunk of the media is uploaded using
twitterApi.mediaService.uploadAppend(...)
. - After the full media is uploaded,
twitterApi.mediaService.uploadFinalize(...)
finalizes the upload. - After finalizing the upload, it may be necessary to wait for Twitter to process the upload to proceed with the Tweet creation.
twitterApi.mediaService.uploadStatus(...)
can be used to periodically poll for updates of the media processing operation until a succeeded status is returned and the media can be attached to the Tweet.
In this example, we use package:mime to determine the MIME type of the media file.
twitterApi
refers to an instance of TwitterApi
from this package.
/// Uploads a media file to Twitter.
///
/// Returns the `mediaId` string that can be used when composing a Tweet.
/// Returns `null` if the [media] file is invalid.
///
/// Throws an exception when a request returns an error or times out.
Future<String> upload(File media) async {
final List<int> mediaBytes = media.readAsBytesSync();
final int totalBytes = mediaBytes.length;
final String mediaType = mime(media.path);
if (totalBytes == 0 || mediaType == null) {
// unknown type or empty file
return null;
}
// initialize the upload
final UploadInit uploadInit = await twitterApi.mediaService.uploadInit(
totalBytes: totalBytes,
mediaType: mediaType,
);
final String mediaId = uploadInit.mediaIdString;
// `splitList` splits the media bytes into lists with the max length of
// 500000 (the max chunk size in bytes)
final List<List<int>> mediaChunks = splitList<int>(
mediaBytes,
_maxChunkSize,
);
// upload each chunk
for (int i = 0; i < mediaChunks.length; i++) {
final List<int> chunk = mediaChunks[i];
await twitterApi.mediaService.uploadAppend(
mediaId: mediaId,
media: chunk,
segmentIndex: i,
);
}
// finalize the upload
final UploadFinalize uploadFinalize =
await twitterApi.mediaService.uploadFinalize(mediaId: mediaId);
if (uploadFinalize.processingInfo?.pending ?? false) {
// asynchronous upload of media
// we have to wait until twitter has processed the upload
final UploadStatus finishedStatus = await _waitForUploadCompletion(
mediaId: mediaId,
sleep: uploadFinalize.processingInfo.checkAfterSecs,
);
return finishedStatus?.mediaIdString;
}
// media has been uploaded and processed
return uploadFinalize.mediaIdString;
}
/// Concurrently requests the status of an upload until the uploaded
/// succeeded and waits the suggested time between each call.
///
/// Returns `null` if the upload failed.
Future<UploadStatus> _waitForUploadCompletion({
@required String mediaId,
@required int sleep,
}) async {
await Future.delayed(Duration(seconds: sleep));
final UploadStatus uploadStatus =
await twitterApi.mediaService.uploadStatus(mediaId: mediaId);
if (uploadStatus?.processingInfo?.succeeded == true) {
// upload processing has succeeded
return uploadStatus;
} else if (uploadStatus?.processingInfo?.inProgress == true) {
// upload is still processing, need to wait longer
return _waitForUploadCompletion(
mediaId: mediaId,
sleep: uploadStatus.processingInfo.checkAfterSecs,
);
} else {
return null;
}
}
/// Splits the [list] into smaller lists with a max [length].
List<List<T>> splitList<T>(List<T> list, int length) {
final List<List<T>> chunks = [];
Iterable<T> chunk;
do {
final List<T> remainingEntries = list.sublist(
chunks.length * length,
);
if (remainingEntries.isEmpty) {
break;
}
chunk = remainingEntries.take(length);
chunks.add(List<T>.from(chunk));
} while (chunk.length == length);
return chunks;
}