I'm trying to build my own music player for Android.
For a listing I need the metadata of the songs, so I'm using the MediaMetadataRetriever to extract them, but the retriever is unable to extract the metadata for most of the songs (~630 of 669). In my PC music software, the metadata are visible and in the Windows Explorer as well. If I open the properties of the songs, I can modify them and if I load the changed files on the phone, it still doesn't work. By the way: If I use any other player on the phone, it is always able to load the metadata.
Is the MediaMetadataRetriever buggy and should I use some other libraries for extracting or am I doing something wrong?
This question is related, but doesn't really help to solve my problem.
Here is some of my code as an example:
MediaMetadataRetriever lMetaData = new MediaMetadataRetriever();
lMetaData.setDataSource(mAbsolutePath);
mArtist = lMetaData.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ARTIST);
mTitle = lMetaData.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE);
mAlbum = lMetaData.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ALBUM);
mAlbumArtist = lMetaData.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ALBUMARTIST);
mGenre = lMetaData.extractMetadata(MediaMetadataRetriever.METADATA_KEY_GENRE);
mSongNumber = lMetaData.extractMetadata(MediaMetadataRetriever.METADATA_KEY_CD_TRACK_NUMBER);
mYear = lMetaData.extractMetadata(MediaMetadataRetriever.METADATA_KEY_YEAR);
if(mArtist == null)
mArtist = new String("");
if(mTitle == null)
mTitle = new String("");
if(mAlbum == null)
mAlbum = new String("");
if(mAlbumArtist == null)
mAlbumArtist = new String("");
if(mGenre == null)
mGenre = new String("");
if(mSongNumber == null)
mSongNumber = new String("");
if(mYear == null)
mYear = new String("");
MediaMetadataRetriever only supports a handful of formats. I recommend FFmpegMediaMetadataRetriever. It supports several additional formats and protocols.
Related
how to play multiple videos one after the another in xamarin cross platform form c#
I have tried using list, array but the problem is that the last video only gets played, rest videos just doesnt play.
MediaQueue mq = new MediaQueue();
MediaFile mf = new MediaFile();
if (PlayStopButtonText.Text == "Play")
{
/*string videoUrl1 = "https://archive.org/download/BigBuckBunny_328/BigBuckBunny_512kb.mp4";
string videoUrl = "https://sec.ch9.ms/ch9/e68c/690eebb1-797a-40ef-a841-c63dded4e68c/Cognitive-Services-Emotion_high.mp4";
CrossMediaManager.Current.Play(videoUrl,MediaFileType.Video,ResourceAvailability.Remote);
//CrossMediaManager.Current.PlayNext();*/
mf.Url = videoUrll;
mf.Type = MediaFileType.Video;
mq.Insert(0,mf);
mf.Url = videoUrl;
mf.Type = MediaFileType.Video;
mq.Insert(1, mf);
foreach(var item in mq)
{
CrossMediaManager.Current.Play(item.Url,MediaFileType.Video);
}
}
I am developing an Android app for TV using the Leanback library. I have an HLS video stream with an srt subtitle from a URI. I am using ExoPlayer version 2.5.4 as used in this example app. I created my MediaSource using:
private MediaSource onCreateMediaSource(Uri uri, Uri subtitleUri) {
String userAgent = Util.getUserAgent(mContext, "ExoPlayerAdapter");
MediaSource videoSource = new HlsMediaSource(uri,
new DefaultDataSourceFactory(mContext, userAgent),
null,
null);
Format subtitleFormat = Format.createTextSampleFormat(
"1", MimeTypes.APPLICATION_SUBRIP, 0, "en");
MediaSource subtitleSource = new SingleSampleMediaSource(
subtitleUri,
new DefaultDataSourceFactory(mContext, userAgent),
subtitleFormat, C.TIME_UNSET);
MergingMediaSource mergedSource =
new MergingMediaSource(videoSource, subtitleSource);
return mergedSource;
}
In my PlaybackTransportControlGlue, I have a PlaybackControlsRow.ClosedCaptioningAction. When this button is clicked, what do I write in the action dispatcher to toggle the closed captions?
I tried this:
#Override
public void onActionClicked(Action action) {
if (action == mClosedCaptioningAction) {
DefaultTrackSelector trackSelector = mAdapter.getTrackSelector();
int rendererIndex = 0;
if (mClosedCaptioningAction.getIndex() == PlaybackControlsRow.ClosedCaptioningAction.INDEX_ON) {
MappingTrackSelector.MappedTrackInfo mappedTrackInfo = trackSelector.getCurrentMappedTrackInfo();
TrackGroupArray textGroups = mappedTrackInfo.getTrackGroups(rendererIndex);
int groupIndex = 0;
trackSelector.setRendererDisabled(rendererIndex, false);
MappingTrackSelector.SelectionOverride override =
new MappingTrackSelector.SelectionOverride(mTrackFactory, groupIndex, 0);
trackSelector.setSelectionOverride(rendererIndex, textGroups, override);
} else {
trackSelector.setRendererDisabled(rendererIndex, true);
trackSelector.clearSelectionOverrides();
}
}
super.onActionClicked(action);
}
I got this error:
E/gralloc: unregister FBM buffer
Okay I just answered a question which got text tracks working in a simple way here. This works for adaptive files (like HLS), but I would have to modify it to get it working with progressive files (like an .mp4 that you've merged with an .srt file).
It's at least a starting point.
I'd like to circle back around and get it fully working for you, but I think it may be a matter of working with the track index and tweaking the override so that it doesn't use the AdaptiveFactory (from the below line).
TrackSelection.Factory factory = tracks.length == 1
? new FixedTrackSelection.Factory()
: new AdaptiveTrackSelection.Factory(BANDWIDTH_METER);
We have it fully working in our code for both HLS and progressive, but our implementation is wrapped in a lot of additional architecture which might make it even harder to understand the core components.
I am trying to create note in a shared notebook which is already shared with me. The notebook is shared with me with priveleges to modify its contents (SharedNotebookPrivilegeLevel.MODIFY_NOTEBOOK_PLUS_ACTIVITY), and I can create notes in that notebook using Evernote web client. But when I trying to create a note using SDK I received an EDAMUserException(errorCode:PERMISSION_DENIED, parameter:authenticationToken). Below is my code
Note note = new Note();
note.setContent(EvernoteUtil.NOTE_PREFIX + content + EvernoteUtil.NOTE_SUFFIX);
note.setTitle(title);
EvernoteSession evernoteSession = EvernoteSession.getInstance();
List<LinkedNotebook> linkedNotebooks = evernoteSession
.getEvernoteClientFactory()
.getNoteStoreClient()
.listLinkedNotebooks();
LinkedNotebook current = null;
for (LinkedNotebook linkedNotebook : linkedNotebooks) {
if (linkedNotebook.getShareName().equals(notebookName)) {
current = linkedNotebook;
}
}
if (current == null)
throw new IllegalStateException();
THttpClient tHttpClient = new THttpClient(current.getNoteStoreUrl());
TBinaryProtocol tBinaryProtocol = new TBinaryProtocol(tHttpClient);
NoteStore.Client client = new NoteStore.Client(tBinaryProtocol, tBinaryProtocol);
AuthenticationResult authenticationResult = client.authenticateToSharedNotebook(current.getShareKey(),
evernoteSession.getAuthToken());
String shareToken = authenticationResult.getAuthenticationToken();
SharedNotebook sharedNotebook = client.getSharedNotebookByAuth(shareToken);
String sharedNotebookGuid = sharedNotebook.getNotebookGuid();
note.setGuid(sharedNotebookGuid);
Note createdNote = client.createNote(shareToken, note);
return createdNote;
I've used this article to create notes.
One mistake I found is that
note.setGuid(sharedNotebookGuid);
has to be
note.setNotebookGuid(sharedNotebookGuid);
I'm building an android app using Xamarin. The requirement of the app is to capture video from the camera and encode the video to send it across to a server.
Initially, I was using an encoder library on the server-side to encode recorded video but it was proving to be extremely unreliable and inefficient especially for large-sized video files. I have posted my issues on another thread here
I then decided to encode the video on the client-side and then send it to the server. I've found encoding to be a bit complicated and there isn't much information available on how this can be done. So, I searched for the only way I knew how to encode a video that is by using FFmpeg codec. I've found some solutions. There's a project on GitHub that demonstrates how FFmpeg is used inside a Xamarin android project. However, running the solution doesn't give any output. The project has a binary FFmpeg file which is installed to the phone directory using the code below:
_ffmpegBin = InstallBinary(XamarinAndroidFFmpeg.Resource.Raw.ffmpeg, "ffmpeg", false);
Below is the example code for encoding video into a different set of outputs:
_workingDirectory = Android.OS.Environment.ExternalStorageDirectory.AbsolutePath;
var sourceMp4 = "cat1.mp4";
var destinationPathAndFilename = System.IO.Path.Combine (_workingDirectory, "cat1_out.mp4");
var destinationPathAndFilename2 = System.IO.Path.Combine (_workingDirectory, "cat1_out2.mp4");
var destinationPathAndFilename4 = System.IO.Path.Combine (_workingDirectory, "cat1_out4.wav");
if (File.Exists (destinationPathAndFilename))
File.Delete (destinationPathAndFilename);
CreateSampleFile(Resource.Raw.cat1, _workingDirectory, sourceMp4);
var ffmpeg = new FFMpeg (this, _workingDirectory);
var sourceClip = new Clip (System.IO.Path.Combine(_workingDirectory, sourceMp4));
var result = ffmpeg.GetInfo (sourceClip);
var br = System.Environment.NewLine;
// There are callbacks based on Standard Output and Standard Error when ffmpeg binary is running as a process:
var onComplete = new MyCommand ((_) => {
RunOnUiThread(() =>_logView.Append("DONE!" + br + br));
});
var onMessage = new MyCommand ((message) => {
RunOnUiThread(() =>_logView.Append(message + br + br));
});
var callbacks = new FFMpegCallbacks (onComplete, onMessage);
// 1. The idea of this first test is to show that video editing is possible via FFmpeg:
// It results in a 150x150 movie that eventually zooms on a cat ear. This is desaturated, and there's a fade-in.
var filters = new List<VideoFilter> ();
filters.Add (new FadeVideoFilter ("in", 0, 100));
filters.Add(new CropVideoFilter("150","150","0","0"));
filters.Add(new ColorVideoFilter(1.0m, 1.0m, 0.0m, 0.5m, 1.0m, 1.0m, 1.0m, 1.0m));
var outputClip = new Clip (destinationPathAndFilename) { videoFilter = VideoFilter.Build (filters) };
outputClip.H264_CRF = "18"; // It's the quality coefficient for H264 - Default is 28. I think 18 is pretty good.
ffmpeg.ProcessVideo(sourceClip, outputClip, true, new FFMpegCallbacks(onComplete, onMessage));
//2. This is a similar version in command line only:
string[] cmds = new string[] {
"-y",
"-i",
sourceClip.path,
"-strict",
"-2",
"-vf",
"mp=eq2=1:1.68:0.3:1.25:1:0.96:1",
destinationPathAndFilename2,
"-acodec",
"copy",
};
ffmpeg.Execute (cmds, callbacks);
// 3. This lists codecs:
string[] cmds3 = new string[] {
"-codecs",
};
ffmpeg.Execute (cmds, callbacks);
// 4. This convers to WAV
// Note that the cat movie just has some silent house noise.
ffmpeg.ConvertToWaveAudio(sourceClip, destinationPathAndFilename4, 44100, 2, callbacks, true);
I have tried different commands but no output file is generated. I have tried to use another project found here but this one has the same issue. I don't get any errors but no output file is generated. I'm really hoping someone can help me find a way I can manage to use FFmpeg in my project or some way to compress video to transport it to the server.
I will really appreciate if someone can point me in the right direction.
Just figure how to get the output by adding the permission in AndroidManifest file.
android.permission.WRITE_EXTERNAL_STORAG
Please read the update on the repository, it says that there is a second package, Xamarin.Android.MP4Transcoder for Android 6.0 onwards.
Install NuGet https://www.nuget.org/packages/Xamarin.Android.MP4Transcoder/
await Xamarin.MP4Transcoder.Transcoder
.For720pFormat()
.ConvertAsync(inputFile, ouputFile, f => {
onProgress?.Invoke((int)(f * (double)100), 100);
});
return ouputFile;
For Previous Android versions
Soruce Code https://github.com/neurospeech/xamarin-android-ffmpeg
Install-Package Xamarin.Android.FFmpeg
Use this as template, this lets you log output as well as calculates progress.
You can take a look at source, this one downloads ffmpeg and verifies sha1 hash on first use.
public class VideoConverter
{
public VideoConverter()
{
}
public File ConvertFile(Context contex,
File inputFile,
Action<string> logger = null,
Action<int,int> onProgress = null)
{
File ouputFile = new File(inputFile.CanonicalPath + ".mpg");
ouputFile.DeleteOnExit();
List<string> cmd = new List<string>();
cmd.Add("-y");
cmd.Add("-i");
cmd.Add(inputFile.CanonicalPath);
MediaMetadataRetriever m = new MediaMetadataRetriever();
m.SetDataSource(inputFile.CanonicalPath);
string rotate = m.ExtractMetadata(Android.Media.MetadataKey.VideoRotation);
int r = 0;
if (!string.IsNullOrWhiteSpace(rotate)) {
r = int.Parse(rotate);
}
cmd.Add("-b:v");
cmd.Add("1M");
cmd.Add("-b:a");
cmd.Add("128k");
switch (r)
{
case 270:
cmd.Add("-vf scale=-1:480,transpose=cclock");
break;
case 180:
cmd.Add("-vf scale=-1:480,transpose=cclock,transpose=cclock");
break;
case 90:
cmd.Add("-vf scale=480:-1,transpose=clock");
break;
case 0:
cmd.Add("-vf scale=-1:480");
break;
default:
break;
}
cmd.Add("-f");
cmd.Add("mpeg");
cmd.Add(ouputFile.CanonicalPath);
string cmdParams = string.Join(" ", cmd);
int total = 0;
int current = 0;
await FFMpeg.Xamarin.FFMpegLibrary.Run(
context,
cmdParams
, (s) => {
logger?.Invoke(s);
int n = Extract(s, "Duration:", ",");
if (n != -1) {
total = n;
}
n = Extract(s, "time=", " bitrate=");
if (n != -1) {
current = n;
onProgress?.Invoke(current, total);
}
});
return ouputFile;
}
int Extract(String text, String start, String end)
{
int i = text.IndexOf(start);
if (i != -1)
{
text = text.Substring(i + start.Length);
i = text.IndexOf(end);
if (i != -1)
{
text = text.Substring(0, i);
return parseTime(text);
}
}
return -1;
}
public static int parseTime(String time)
{
time = time.Trim();
String[] tokens = time.Split(':');
int hours = int.Parse(tokens[0]);
int minutes = int.Parse(tokens[1]);
float seconds = float.Parse(tokens[2]);
int s = (int)seconds * 100;
return hours * 360000 + minutes * 60100 + s;
}
}
In my application Youtube videos were playing perfectly in InApp. But certainly 2 days before I got the following alert message "Sorry, this video cannot be played" while playing the video and videos are not playing. I have tried different youtube video links, but no hope. If I use this code :
Intent browserIntent = new Intent(Intent.ACTION_VIEW,
Uri.parse("http://www.youtube.com/embed/Ai47z6qh8S0"));
startActivity(browserIntent);
Videos are playing then in browser. But I need this video to be played inside the application.
Previously I used the following code to create the youtube url
public static String calculateYouTubeUrl(String pYouTubeFmtQuality, boolean pFallback,
String pYouTubeVideoId) throws IOException,
ClientProtocolException, UnsupportedEncodingException {
String lUriStr = null;
HttpClient lClient = new DefaultHttpClient();
HttpGet lGetMethod = new HttpGet(OpenYouTubePlayerActivity.YOUTUBE_VIDEO_INFORMATION_URL +
pYouTubeVideoId);
HttpResponse lResp = null;
lResp = lClient.execute(lGetMethod);
ByteArrayOutputStream lBOS = new ByteArrayOutputStream();
String lInfoStr = null;
lResp.getEntity().writeTo(lBOS);
lInfoStr = new String(lBOS.toString("UTF-8"));
String[] lArgs=lInfoStr.split("&");
Map<String,String> lArgMap = new HashMap<String, String>();
for(int i=0; i<lArgs.length; i++){
String[] lArgValStrArr = lArgs[i].split("=");
if(lArgValStrArr != null){
if(lArgValStrArr.length >= 2){
lArgMap.put(lArgValStrArr[0], URLDecoder.decode(lArgValStrArr[1]));
}
}
}
//Find out the URI string from the parameters
//Populate the list of formats for the video
String lFmtList = URLDecoder.decode(lArgMap.get("fmt_list"));
ArrayList<Format> lFormats = new ArrayList<Format>();
if(null != lFmtList){
String lFormatStrs[] = lFmtList.split(",");
for(String lFormatStr : lFormatStrs){
Format lFormat = new Format(lFormatStr);
lFormats.add(lFormat);
}
}
//Populate the list of streams for the video
String lStreamList = lArgMap.get("url_encoded_fmt_stream_map");
if(null != lStreamList){
String lStreamStrs[] = lStreamList.split(",");
ArrayList<VideoStream> lStreams = new ArrayList<VideoStream>();
for(String lStreamStr : lStreamStrs){
VideoStream lStream = new VideoStream(lStreamStr);
lStreams.add(lStream);
}
//Search for the given format in the list of video formats
// if it is there, select the corresponding stream
// otherwise if fallback is requested, check for next lower format
int lFormatId = Integer.parseInt(pYouTubeFmtQuality);
Format lSearchFormat = new Format(lFormatId);
while(!lFormats.contains(lSearchFormat) && pFallback ){
int lOldId = lSearchFormat.getId();
int lNewId = getSupportedFallbackId(lOldId);
if(lOldId == lNewId){
break;
}
lSearchFormat = new Format(lNewId);
}
int lIndex = lFormats.indexOf(lSearchFormat);
if(lIndex >= 0){
VideoStream lSearchStream = lStreams.get(lIndex);
lUriStr = lSearchStream.getUrl();
}
}
//Return the URI string. It may be null if the format (or a fallback format if enabled)
// is not found in the list of formats for the video
return lUriStr;
}
Please help me to figure out this issue.
Thanks Santanu
This kind of youtube link wont work on android application, although it works fine on browsers. To make it work on an application you need to get the RTSP link of the video first.
Refer to this thread and see if you can find a solution there.
Example of youtube link working on android(I have tested this one):
rtsp://v2.cache6.c.youtube.com/CjgLENy73wIaLwn_aij76iwMRRMYESARFEIJbXYtZ29vZ2xlSARSB3JlbGF0ZWRgnfqw4Jajx8xPDA==/0/0/0/video.3gp
<iframe width="637" height="358" src="http://www.youtube.com/embed/Ai47z6qh8S0?fs=1&feature=oembed" frameborder="0" allowfullscreen=""></iframe>
try this or
you need to have hardware acceleration turned on
Intent i = new Intent(Intent.ACTION_VIEW);
i.setData(Uri.parse("Video url"));
VideoActivity.this.startActivity(i);
//And add below code in Manifest.xml file.
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />