Skip to content

Commit

Permalink
Plugin: Add web support & improve structure
Browse files Browse the repository at this point in the history
  • Loading branch information
alexmercerind committed Jan 30, 2022
1 parent 79c2bd5 commit 9ce4f4b
Show file tree
Hide file tree
Showing 5 changed files with 415 additions and 121 deletions.
153 changes: 32 additions & 121 deletions lib/flutter_media_metadata.dart
Original file line number Diff line number Diff line change
@@ -1,121 +1,32 @@
import 'dart:io';
import 'dart:convert';
import 'dart:typed_data';
import 'package:flutter/services.dart';

var _kChannel = const MethodChannel('flutter_media_metadata');

class MetadataRetriever {
static Future<Metadata> fromFile(
File file, {
bool createNewInstance: false,
}) async {
var metadata = await _kChannel.invokeMethod(
'MetadataRetriever',
{
'filePath': file.path,
'createNewInstance': createNewInstance,
},
);
metadata['filePath'] = file.path;
return Metadata.fromJson(metadata);
}
}

class Metadata {
final String? trackName;
final List<String>? trackArtistNames;
final String? albumName;
final String? albumArtistName;
final int? trackNumber;
final int? albumLength;
final int? year;
final String? genre;
final String? authorName;
final String? writerName;
final int? discNumber;
final String? mimeType;
final int? trackDuration;
final int? bitrate;
final Uint8List? albumArt;
final String filePath;

const Metadata({
this.trackName,
this.trackArtistNames,
this.albumName,
this.albumArtistName,
this.trackNumber,
this.albumLength,
this.year,
this.genre,
this.authorName,
this.writerName,
this.discNumber,
this.mimeType,
this.trackDuration,
this.bitrate,
this.albumArt,
required this.filePath,
});

factory Metadata.fromJson(dynamic map) => Metadata(
trackName: map['metadata']['trackName'],
trackArtistNames: map['metadata']['trackArtistNames'] != null
? map['metadata']['trackArtistNames'].split('/')
: null,
albumName: map['metadata']['albumName'],
albumArtistName: map['metadata']['albumArtistName'],
trackNumber: _parse(map['metadata']['trackNumber']),
albumLength: _parse(map['metadata']['albumLength']),
year: _parse(map['metadata']['year']),
genre: map['genre'],
authorName: map['metadata']['authorName'],
writerName: map['metadata']['writerName'],
discNumber: _parse(map['metadata']['discNumber']),
mimeType: map['metadata']['mimeType'],
trackDuration: _parse(map['metadata']['trackDuration']),
bitrate: _parse(map['metadata']['bitrate']),
albumArt: map['albumArt'],
filePath: map['filePath'],
);

Map<String, dynamic> toJson() => {
'trackName': trackName,
'trackArtistNames': trackArtistNames,
'albumName': albumName,
'albumArtistName': albumArtistName,
'trackNumber': trackNumber,
'albumLength': albumLength,
'year': year,
'genre': genre,
'authorName': authorName,
'writerName': writerName,
'discNumber': discNumber,
'mimeType': mimeType,
'trackDuration': trackDuration,
'bitrate': bitrate,
'filePath': filePath,
};

@override
String toString() => JsonEncoder.withIndent(' ').convert(toJson());
}

int? _parse(dynamic value) {
if (value == null) {
return null;
}
if (value is int) {
return value;
} else if (value is String) {
try {
try {
return int.parse(value);
} catch (_) {
return int.parse(value.split('/').first);
}
} catch (_) {}
}
return null;
}
/// ## flutter_media_metadata
///
/// A Flutter plugin to read metadata of media files.
///
/// MIT License.
/// Copyright (c) 2021-2022, Hitesh Kumar Saini <[email protected]>.
///
/// _Minimal Example_
/// ```dart
/// final metadata = MetadataRetriever.fromBytes(byteData);
/// String? trackName = metadata.trackName;
/// List<String>? trackArtistNames = metadata.trackArtistNames;
/// String? albumName = metadata.albumName;
/// String? albumArtistName = metadata.albumArtistName;
/// int? trackNumber = metadata.trackNumber;
/// int? albumLength = metadata.albumLength;
/// int? year = metadata.year;
/// String? genre = metadata.genre;
/// String? authorName = metadata.authorName;
/// String? writerName = metadata.writerName;
/// int? discNumber = metadata.discNumber;
/// String? mimeType = metadata.mimeType;
/// int? trackDuration = metadata.trackDuration;
/// int? bitrate = metadata.bitrate;
/// Uint8List? albumArt = metadata.albumArt;
/// ```
///
library flutter_media_metadata;

export 'package:flutter_media_metadata/src/flutter_media_metadata_native.dart'
if (dart.library.html) 'package:flutter_media_metadata/src/flutter_media_metadata_web.dart';
export 'package:flutter_media_metadata/src/models/metadata.dart';
56 changes: 56 additions & 0 deletions lib/src/flutter_media_metadata_native.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/// This file is a part of flutter_media_metadata (https://github.com/alexmercerind/flutter_media_metadata).
///
/// Copyright (c) 2021-2022, Hitesh Kumar Saini <[email protected]>.
/// All rights reserved.
/// Use of this source code is governed by MIT license that can be found in the LICENSE file.
import 'dart:io';
import 'package:flutter/services.dart';

import 'package:flutter_media_metadata/src/models/metadata.dart';

/// ## MetadataRetriever
///
/// Use [MetadataRetriever.fromFile] to extract [Metadata] from a media file.
///
/// ```dart
/// final metadata = MetadataRetriever.fromFile(file);
/// String? trackName = metadata.trackName;
/// List<String>? trackArtistNames = metadata.trackArtistNames;
/// String? albumName = metadata.albumName;
/// String? albumArtistName = metadata.albumArtistName;
/// int? trackNumber = metadata.trackNumber;
/// int? albumLength = metadata.albumLength;
/// int? year = metadata.year;
/// String? genre = metadata.genre;
/// String? authorName = metadata.authorName;
/// String? writerName = metadata.writerName;
/// int? discNumber = metadata.discNumber;
/// String? mimeType = metadata.mimeType;
/// int? trackDuration = metadata.trackDuration;
/// int? bitrate = metadata.bitrate;
/// Uint8List? albumArt = metadata.albumArt;
/// ```
///
class MetadataRetriever {
/// Extracts [Metadata] from a [File]. Works on Windows, Linux, macOS, Android & iOS.
static Future<Metadata> fromFile(File file) async {
var metadata = await _kChannel.invokeMethod(
'MetadataRetriever',
{
'filePath': file.path,
},
);
metadata['filePath'] = file.path;
return Metadata.fromJson(metadata);
}

/// Extracts [Metadata] from [Uint8List]. Works only on Web.
static Future<Metadata> fromBytes(dynamic _) async {
throw UnimplementedError(
'[MetadataRetriever.fromBytes] is not supported on ${Platform.operatingSystem}. This method is only available for web. Use [MetadataRetriever.fromFile] instead.',
);
}
}

var _kChannel = const MethodChannel('flutter_media_metadata');
182 changes: 182 additions & 0 deletions lib/src/flutter_media_metadata_web.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
/// This file is a part of flutter_media_metadata (https://github.com/alexmercerind/flutter_media_metadata).
///
/// Copyright (c) 2021-2022, Hitesh Kumar Saini <[email protected]>.
/// All rights reserved.
/// Use of this source code is governed by MIT license that can be found in the LICENSE file.
// ignore_for_file: missing_js_lib_annotation

import 'dart:async';
import 'dart:convert';
import 'dart:typed_data';
import 'package:js/js.dart';
import 'package:flutter/services.dart';
import 'package:flutter_web_plugins/flutter_web_plugins.dart';

import 'package:flutter_media_metadata/src/models/metadata.dart';

/// ## MetadataRetriever
///
/// Use [MetadataRetriever.fromBytes] to extract [Metadata] from bytes of media file.
///
/// ```dart
/// final metadata = MetadataRetriever.fromBytes(byteData);
/// String? trackName = metadata.trackName;
/// List<String>? trackArtistNames = metadata.trackArtistNames;
/// String? albumName = metadata.albumName;
/// String? albumArtistName = metadata.albumArtistName;
/// int? trackNumber = metadata.trackNumber;
/// int? albumLength = metadata.albumLength;
/// int? year = metadata.year;
/// String? genre = metadata.genre;
/// String? authorName = metadata.authorName;
/// String? writerName = metadata.writerName;
/// int? discNumber = metadata.discNumber;
/// String? mimeType = metadata.mimeType;
/// int? trackDuration = metadata.trackDuration;
/// int? bitrate = metadata.bitrate;
/// Uint8List? albumArt = metadata.albumArt;
/// ```
///
class MetadataRetriever {
static void registerWith(Registrar registrar) {
final MethodChannel channel = MethodChannel(
'flutter_media_metadata',
const StandardMethodCodec(),
registrar,
);
final pluginInstance = MetadataRetriever();
channel.setMethodCallHandler(pluginInstance.handleMethodCall);
}

Future<dynamic> handleMethodCall(MethodCall call) => throw PlatformException(
code: 'Unimplemented',
details:
'flutter_media_metadata for web doesn\'t implement \'${call.method}\'',
);

/// Extracts [Metadata] from a [File]. Works on Windows, Linux, macOS, Android & iOS.
static Future<Metadata> fromFile(dynamic _) async {
throw UnimplementedError(
'[MetadataRetriever.fromFile] is not supported on web. This method is only available for Windows, Linux, macOS, Android or iOS. Use [MetadataRetriever.fromBytes] instead.',
);
}

/// Extracts [Metadata] from [Uint8List]. Works only on Web.
static Future<Metadata> fromBytes(Uint8List bytes) {
final completer = Completer<Metadata>();
MediaInfo(
_Opts(
chunkSize: 256 * 1024,
coverData: true,
format: 'JSON',
),
allowInterop(
(mediainfo) {
mediainfo
.analyzeData(
allowInterop(() => bytes.length),
allowInterop(
(chunkSize, offset) => _Promise(
allowInterop(
(resolve, reject) {
resolve(
bytes.sublist(
offset,
offset + chunkSize,
),
);
},
),
),
),
)
.then(
allowInterop(
(result) {
var rawMetadataJson = jsonDecode(result)['media']['track'];
bool isFound = false;
for (final data in rawMetadataJson) {
if (data['@type'] == 'General') {
isFound = true;
rawMetadataJson = data;
break;
}
}
if (!isFound) {
throw Exception();
}
final metadata = <String, dynamic>{
'metadata': {},
'albumArt': base64Decode(rawMetadataJson['Cover_Data']),
'filePath': null,
};
_kMetadataKeys.forEach((key, value) {
metadata['metadata'][key] = rawMetadataJson[value];
});
completer.complete(Metadata.fromJson(metadata));
},
),
allowInterop(
() {
completer.completeError(Exception());
},
),
);
},
),
allowInterop(
() {
completer.completeError(Exception());
},
),
);
return completer.future;
}
}

@JS('Promise')
class _Promise<T> {
external _Promise(void executor(void resolve(T result), Function reject));
external _Promise then(void onFulfilled(T result), [Function onRejected]);
}

@JS('MediaInfo')
external String MediaInfo(
Object opts,
void Function(_MediaInfo) successCallback,
void Function() erroCallback,
);

@JS()
@anonymous
class _Opts {
external int chunkSize;
external bool coverData;
external String format;

external factory _Opts({int chunkSize, bool coverData, String format});
}

@JS()
@anonymous
class _MediaInfo {
external _Promise<String> analyzeData(int Function() getSize,
_Promise<Uint8List> promise(int chunkSize, int offset));

external factory _MediaInfo();
}

const _kMetadataKeys = <String, String>{
"trackName": "Track",
"trackArtistNames": "Performer",
"albumName": "Album",
"albumArtistName": "Album_Performer",
"trackNumber": "Track_Position",
"albumLength": "Track_Position_Total",
"year": "Recorded_Date",
"genre": "Genre",
"writerName": "WrittenBy",
"trackDuration": "Duration",
"bitrate": "OverallBitRate",
};
Loading

0 comments on commit 9ce4f4b

Please sign in to comment.