How can I play downloaded mp3 files with Xamarin.Forms iOS - ios

I want to play mp3 files with the program that I will perform. I can play the file without downloading the files, but I can not play the file which I downloaded. Can you help me with this?
I am working on the Xamarin.Forms project. I download the mp3 file from the internet via API. But I can not figure out where the downloaded file is specifically registered. I share the codes that I wrote on the IOS layer and on the PCL side with you.
Note: I can check that the download is successful and check the download status.
Note2: I use Xam.Plugins.DownloadManager for the download.
The code that I wrote at the IOS layer.
public void SetDownloadPath()
{
CrossDownloadManager.Current.PathNameForDownloadedFile = new System.Func<IDownloadFile, string>(file =>
{
string fileName = (new NSUrl(file.Url, false)).LastPathComponent;
return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), fileName);
});
}
The code I wrote in ViewModel.
public async Task<DownloadItem> AddBookDownload(BookInfo _bookInfo)
{
var downloadItem = new DownloadItem
{
BookName = _bookInfo.Title,
DownloadID = _bookInfo.ID,
DistinctionCount = _bookInfo.SectionCount
};
var sections = await LibraryClient.GetBookSections(_bookInfo.ID);
downloadItem.Sections = sections.Data;
if (downloadItem.Sections == null || downloadItem.Sections.Count <0)
{
return null;
}
var linkFile =
CrossDownloadManager.Current.CreateDownloadFile(downloadItem.Sections.FirstOrDefault()
?.GetLink()
.ToString());
downloadItem.DownloadedTaskList.Add(linkFile.GetHashCode(), downloadItem);
linkFile.PropertyChanged += (sender, e) =>
{
// Update UI text-fields
var downloadFile = (IDownloadFile) sender;
switch (e.PropertyName)
{
case nameof(IDownloadFile.Status):
Device.BeginInvokeOnMainThread(() =>
{
downloadItem.DownloadState = downloadFile.Status;
Debug.WriteLine("Download Status: " + downloadFile.Status);
});
break;
case nameof(IDownloadFile.StatusDetails):
Device.BeginInvokeOnMainThread(() =>
{
Debug.WriteLine("Download Details: " + downloadFile.StatusDetails);
});
break;
case nameof(IDownloadFile.TotalBytesExpected):
Device.BeginInvokeOnMainThread(() =>
{
Debug.WriteLine("BytesExpected" + downloadFile.TotalBytesExpected);
});
break;
case nameof(IDownloadFile.TotalBytesWritten):
Device.BeginInvokeOnMainThread(() =>
{
Debug.WriteLine("BytesWritten" + downloadFile.TotalBytesWritten);
});
break;
}
// Update UI if download-status changed.
if (e.PropertyName == "Status")
switch (((IDownloadFile) sender).Status)
{
case DownloadFileStatus.COMPLETED:
downloadItem.DownloadState = DownloadFileStatus.COMPLETED;
DistinctionCount = downloadItem.DistinctionCount;
BookName = downloadItem.BookName;
DownloadedBooks.Add(downloadItem);
NativeServices.DownloadService.SaveDownloadsItem(downloadItem);
NativeServices.MediaPlayerService.PlayFromFile(Path.GetFileName(CrossDownloadManager.Current.PathNameForDownloadedFile.ToString()));
Debug.WriteLine("Download Completed");
break;
case DownloadFileStatus.FAILED:
downloadItem.DownloadState = DownloadFileStatus.FAILED;
Debug.WriteLine("Download Failed");
break;
case DownloadFileStatus.CANCELED:
Device.BeginInvokeOnMainThread(() => { Debug.WriteLine("Download Cancelled"); });
break;
}
// Update UI while donwloading.
if (e.PropertyName == "TotalBytesWritten" || e.PropertyName == "TotalBytesExpected")
{
var bytesExpected = ((IDownloadFile) sender).TotalBytesExpected;
var bytesWritten = ((IDownloadFile) sender).TotalBytesWritten;
if (bytesExpected > 0)
{
var percentage = Math.Round(bytesWritten / bytesExpected * 100);
Device.BeginInvokeOnMainThread(() => { Debug.WriteLine("Downloading" + percentage + "%"); });
}
}
};
CrossDownloadManager.Current.Start(linkFile);
return downloadItem;
}

You can find the Audio file Xamrin form sample on below URL :
Audio File Integration Xamarin Forms

Related

IOS error: Media stream has no audio tracks createMediaStreamSource

I tried to mix remote audio tracks in one track and replace my local stream but I got the following error
InvalidStateError: Media stream has no audio tracks createMediaStreamSource
Note: This happen in IOS and I used sipjs, angular , Ionic, and IOSRTC
async mixNWayCall(nawayCall: NWayCall) {
var receivedTracks = [];
nawayCall.lines.forEach((line: Line) => {
if (line !== null && line !== undefined) {
const sessionDescriptionHandler = line.sipSession.sessionDescriptionHandler;
if (!(sessionDescriptionHandler instanceof Web.SessionDescriptionHandler)) {
throw new Error("Session's session description handler not instance of SessionDescriptionHandler.");
}
const peerConnection = sessionDescriptionHandler.peerConnection;
if (!peerConnection) {
throw new Error("Peer connection closed.");
}
peerConnection.getReceivers().forEach((receiver) => {
if (receiver.track) {
receivedTracks.push(receiver.track);
}
});
}
});
let context = new AudioContext();
let allReceivedMediaStreams = new MediaStream();
nawayCall.lines.forEach((line: Line) => {
if (line !== null && line !== undefined) {
let mixedOutput = context.createMediaStreamDestination();
const sessionDescriptionHandler = line.sipSession.sessionDescriptionHandler;
if (!(sessionDescriptionHandler instanceof Web.SessionDescriptionHandler)) {
throw new Error("Session's session description handler not instance of SessionDescriptionHandler.");
}
const senderPeerConnection = sessionDescriptionHandler.peerConnection;
if (!senderPeerConnection) { throw new Error("Peer connection closed."); }
senderPeerConnection.getReceivers().forEach((receiver) => {
receivedTracks.forEach((track) => {
allReceivedMediaStreams.addTrack(receiver.track);
console.log(receiver.track.id, ' receiver.track.id');
console.log(track.id, ' track.id');
if (receiver.track.id !== track.id) {
var sourceStream = context.createMediaStreamSource(new MediaStream([track]));
sourceStream.connect(mixedOutput);
}
});
});
senderPeerConnection.getSenders().forEach((sender) => {
nawayCall.mergeTracks.push(sender.track);
let senderSourceStream = context.createMediaStreamSource(new MediaStream([sender.track]));
senderSourceStream.connect(mixedOutput);
sender.replaceTrack(mixedOutput.stream.getTracks()[0])
});
senderPeerConnection.getSenders()[0].replaceTrack(mixedOutput.stream.getTracks()[0]);
}
});
nawayCall.lines.forEach(async (line: Line) => {
if (line.held) await this.lineService.onResume(line.id, true);
});
nawayCall.held = false;
if (nawayCall.media.mute)
await this.lineService.onNWayCallUnmute(nawayCall.id);
}
}
from the code in the description I got an error that no audio tracks and
I expected to mix audio tracks in one audio track then merge the call

How to make a listview work in Xamarin (VS 2022)

Here's this code. It sort of runs but then abends. How do I make the choice work? And additionally, I wrote (copied) this code about 18 months ago, so I am vague as to why it seems so backward:
void SwitchNumber()
{
var dialogView = LayoutInflater.Inflate(Resource.Layout.list_view, null);
Android.App.AlertDialog alertDialog;
listview = dialogView.FindViewById<ListView>(Resource.Id.listview);
textview = dialogView.FindViewById<TextView>(Resource.Id.textview);
var items = new string[] { "1","2","3" };
var adapter = new ArrayAdapter<string>(this, Android.Resource.Layout.SimpleListItem1, items);
using (var dialog = new Android.App.AlertDialog.Builder(this))
{
listview.Adapter = adapter;
listview.ItemClick += Listview_ItemClick;
dialog.SetTitle("Switch Number");
dialog.SetMessage("Click on the number you want to switch to");
dialog.SetView(dialogView);
string newNumber = string.Empty;
dialog.SetNegativeButton("Cancel", (s, a) => { });
dialog.SetPositiveButton("OK", (s, a) =>
{
switch (prefs.GetInt("selectitemforAlert", 0))
{
case 0:
newNumber = "1";
break;
case 1:
newNumber = "2";
break;
case 2:
newNumber = "3";
break;
default:
currentNumber = "1";
break;
}
}//switch
);
if (newNumber == currentNumber) return;
ChangeNumber(newNumber);
alertDialog = dialog.Create();
// }
}
//using
dialogView.FindViewById<ListView>(Resource.Id.listview).Adapter = adapter;
alertDialog.Show();
listview.Adapter = adapter;
listview.ItemClick += Listview_ItemClick;
}
The list does appear but I get a null object reference.

WebRTC connection does not resume after mobile browser is backgrounded

I have a web application running on Safari on an iPad displaying a live WebRTC video stream. When the user switches away from Safari for a few seconds, and then switches back, the <video> element just shows a black rectangle.
I have added logging to the onsignalingstatechange handler, and checked the console logs for any apparent errors after resuming Safari, but there is nothing obvious indicating the failure.
How can I recover/resume/restart the stream after the user switches back to Safari?
Here is my cargo cult WebRTC code, for reference:
export default class WebRtcPlayer {
static server = "http://127.0.0.1:8083";
server = null;
stream = null;
channel = null;
webrtc = null;
mediastream = null;
video = null;
constructor(id, stream, channel) {
this.server = WebRtcPlayer.server;
this.video = document.getElementById(id);
this.stream = stream;
this.channel = channel;
this.video.addEventListener("loadeddata", () => {
this.video.play();
});
this.video.addEventListener("error", () => {
console.error("video error");
});
this.play();
}
getStreamUrl() {
// RTSPtoWeb only, not RTSPtoWebRTC
return `${this.server}/stream/${this.stream}/channel/${this.channel}/webrtc`;
}
async play() {
console.log("webrtc play");
this.mediastream = new MediaStream();
this.video.srcObject = this.mediastream;
this.webrtc = new RTCPeerConnection({
iceServers: [{
urls: ["stun:stun.l.google.com:19302"],
}],
sdpSemantics: "unified-plan"
});
this.webrtc.onnegotiationneeded = this.handleNegotiationNeeded.bind(this);
this.webrtc.onsignalingstatechange = this.handleSignalingStateChange.bind(this);
this.webrtc.ontrack = this.handleTrack.bind(this);
this.webrtc.addTransceiver("video", {
"direction": "sendrecv",
});
}
async handleNegotiationNeeded() {
console.log("handleNegotiationNeeded");
let offer = await this.webrtc.createOffer({
offerToReceiveAudio: false,
offerToReceiveVideo: true
});
await this.webrtc.setLocalDescription(offer);
}
async handleSignalingStateChange() {
console.log(`handleSignalingStateChange ${this.webrtc.signalingState}`);
switch (this.webrtc.signalingState) {
case "have-local-offer":
let formData = new FormData();
formData.append("data", btoa(this.webrtc.localDescription.sdp));
const response = await fetch(this.getStreamUrl(), {
method: "POST",
body: formData,
});
this.webrtc.setRemoteDescription(new RTCSessionDescription({
type: "answer",
sdp: atob(await response.text()),
}));
break;
case "stable":
/*
* There is no ongoing exchange of offer and answer underway.
* This may mean that the RTCPeerConnection object is new, in which case both the localDescription and remoteDescription are null;
* it may also mean that negotiation is complete and a connection has been established.
*/
break;
case "closed":
/*
* The RTCPeerConnection has been closed.
*/
break;
default:
console.log(`unhandled signalingState is ${this.webrtc.signalingState}`);
break;
}
}
handleTrack(event) {
console.log("handle track");
this.mediastream.addTrack(event.track);
}
static setServer(serv) {
this.server = serv;
}
}
I'm not sure if it's the best way, but I used the Page Visibility API to subscribe to the visibilitychange event:
constructor(id, stream, channel) {
// ...
document.addEventListener("visibilitychange", () => {
if (document.visibilityState === "visible") {
console.log("Document became visible, restarting WebRTC stream.");
this.play();
}
});
// ...
}

Youtube V3 List API is returning blank items, when video uploading is failed

I am using Youtube v3 PHP API to upload videos on youtube. After uploading I check the status of uploaded video using list API. The list API used to give the video in items array in response irrespective of whatever video upload/processing status maybe. But before 4 days, it suddenly stopped sending video info if video upload gets failed for any reason like duplicate upload. How should I fix this?
public static function uploadVideo_($file, $filename, $target_dir, $params = []){
try {
if (is_array($file)) {
$ret = self::uploadToLocalFromArray_($file, $filename, $target_dir);
} else {
$ret = self::uploadToLocal_($file, $filename, $target_dir);
}
if(!$ret){
throw Exception("Failed to upload video on local");
}
$client = Yii::$app->google->getService();
$youtube = new Google_Service_YouTube($client);
$videoPath = $target_dir.$filename;
$snippet = new Google_Service_YouTube_VideoSnippet();
$snippet->setTitle(#$params['title']);
$snippet->setDescription(#$params['description']);
$snippet->setTags(#$params['tags']);
$snippet->setCategoryId(#$params['category_id']);
// Set the video's status to "public". Valid statuses are "public",
// "private" and "unlisted".
$status = new Google_Service_YouTube_VideoStatus();
$status->privacyStatus = "public";
// Associate the snippet and status objects with a new video resource.
$video = new Google_Service_YouTube_Video();
$video->setSnippet($snippet);
$video->setStatus($status);
$chunkSizeBytes = 1 * 1024 * 1024;
// Setting the defer flag to true tells the client to return a request which can be called
// with ->execute(); instead of making the API call immediately.
$client->setDefer(true);
// Create a request for the API's videos.insert method to create and upload the video.
$insertRequest = $youtube->videos->insert("status,snippet", $video);
// Create a MediaFileUpload object for resumable uploads.
$media = new Google_Http_MediaFileUpload(
$client,
$insertRequest,
'video/*',
null,
true,
$chunkSizeBytes
);
$media->setFileSize(filesize($videoPath));
// Read the media file and upload it chunk by chunk.
$status = false;
$handle = fopen($videoPath, "rb");
while (!$status && !feof($handle)) {
$chunk = fread($handle, $chunkSizeBytes);
$status = $media->nextChunk($chunk);
}
fclose($handle);
$videoId = $status['id'];
// If you want to make other calls after the file upload, set setDefer back to false
// Get Video uploaded or not - alternate
$client->setDefer(false);
while(true){
sleep(1);
# Call the videos.list method to retrieve processingDetails for each video.
$videosResponse = $youtube->videos->listVideos('status, processingDetails', array(
'id' => $videoId,
));
if(empty($videosResponse['items'][0])){
continue;
}
$videoResult = $videosResponse['items'][0];
if($videoResult['processingDetails']['processingStatus'] == "processing") {
continue;
}
if($videoResult['status']['uploadStatus'] == "processed" || $videoResult['status']['uploadStatus'] == "uploaded") {
break;
} else if($videoResult['status']['uploadStatus'] == "deleted") {
throw new Exception("Video has been deleted.");
} else if($videoResult['status']['uploadStatus'] == "failed") {
throw new Exception("Video upload failed: ".$videoResult['status']['failureReason']);
} else if($videoResult['status']['uploadStatus'] == "rejected") {
throw new Exception("Video Rejected: ".$videoResult['status']['rejectionReason']);
}
/*
if($videoResult['processingDetails']['processingStatus'] == "failed") {
throw new Exception("");
} else if($videoResult['processingDetails']['processingStatus'] == "succeeded") {
break;
} else if($videoResult['processingDetails']['processingStatus'] == "terminated") {
return ['status'=>false, 'message'=>$e->getMessage()];
}*/
$client->setDefer(false);
}
unlink($videoPath);
$thumb_high = $status->getSnippet()->getThumbnails()->getHigh();
$thumb_url = str_replace("hq", "sd", $thumb_high->getUrl());
try {
$dim = getimagesize($thumb_url);
$thumb_width = $dim[0];
$thumb_height = $dim[1];
} catch(Exception $e) {
$thumb_url = $thumb_high->getUrl();
$thumb_width = $thumb_high->getWidth();
$thumb_height = $thumb_high->getHeight();
}
return ['status'=>true, 'videoId'=>$videoId, 'thumbnail'=>$thumb_url, 'width'=>$thumb_width, 'height'=>$thumb_height];
} catch (Exception $e) {
return ['status'=>false, 'message'=>$e->getMessage()];
}
return ['status'=>false, 'message'=>'Unknown Reason'];
}

Find and remove file from cache

I have this css which applies background-image: path/to/desktop/build1.png.
So whatever is build1.png on desktop will be the background image. However, in my addon, after I dynamically delete build1.png and replace it with another image that I rename build1.png, the background-image does not update. I tried ctrl+f5 and it didn't work. So I'm thinking after I dynamically replace the image on the desktop I should delete it from cache.
Test case, copy paste to scratchpad with environment browser and hit run, it opens a new tab with a gui for the test case. Hit "Release Img" then hit "applyCss" then hit "Aurora Img" and watch how the background of the div doesn't change.
Here is youtube video demo of this testcase demo'ing the issue: https://www.youtube.com/watch?v=mbGJxHtstrw
var tab = gBrowser.loadOneTab('data:text/html,<div class="profilist-icon-build-1">backround of this span is of icon on desktop</div><input type="button" value="applyCss"><input type="button" value="removeCss"> Change File on Desktop to: <input type="button" value="Release Img"><input type="button" value="Beta Img"><input type="button" value="Aurora Img"><input type="button" value="Nightly Img">', {inBackground:false});
//start - listen for loadOneTab to finish loading per https://gist.github.com/Noitidart/0f076070bc77abd5e406
var mobs = new window.MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
if (mutation.attributeName == 'progress' && tab.getAttribute('progress') == '') {
//alert('tab done loading');
init();
mobs.disconnect();
}
});
});
mobs.observe(tab, {attributes: true});
//end - listen for loadOneTab to finish loading per https://gist.github.com/Noitidart/0f076070bc77abd5e406
function init() {
//run on load of the loadOneTab
var win = tab.linkedBrowser.contentWindow;
var doc = win.document;
var btns = doc.querySelectorAll('input[type=button]');
Array.prototype.forEach.call(btns, function(b) {
b.addEventListener('click', handleBtnClick, false);
});
}
Cu.import('resource://gre/modules/Services.jsm');
Cu.import('resource://gre/modules/osfile.jsm');
var sss = Cc['#mozilla.org/content/style-sheet-service;1'].getService(Ci.nsIStyleSheetService);
var cssBuildIconsStr = '.profilist-icon-build-1 { background-image: url("' + OS.Path.toFileURI(OS.Path.join(OS.Constants.Path.desktopDir, 'build1.png')) + '"); ';
var newURIParam = {
aURL: 'data:text/css,' + encodeURIComponent(cssBuildIconsStr),
aOriginCharset: null,
aBaseURI: null
};
var cssBuildIconsURI = Services.io.newURI(newURIParam.aURL, newURIParam.aOriginCharset, newURIParam.aBaseURI);
function handleBtnClick(e) {
var targ = e.target;
var doc = e.target.ownerDocument;
var win = doc.defaultView;
switch(targ.value) {
case 'applyCss':
if (sss.sheetRegistered(cssBuildIconsURI, sss.AUTHOR_SHEET)) {
win.alert('ERROR: Sheet is already registered! Will not re-register...');
} else {
sss.loadAndRegisterSheet(cssBuildIconsURI, sss.AUTHOR_SHEET);
win.alert('REGISTERED')
}
break;
case 'removeCss':
if (sss.sheetRegistered(cssBuildIconsURI, sss.AUTHOR_SHEET)) {
sss.unregisterSheet(cssBuildIconsURI, sss.AUTHOR_SHEET);
win.alert('UNregged');
} else {
win.alert('ERROR: Sheet is not registered! Nothing to unregister...');
}
break;
case 'Release Img':
xhr('https://raw.githubusercontent.com/Noitidart/Profilist/%2321/bullet_release.png', function(d){saveToDiskAsBuild1(d, win)});
break;
case 'Beta Img':
xhr('https://raw.githubusercontent.com/Noitidart/Profilist/%2321/bullet_beta.png', function(d){saveToDiskAsBuild1(d, win)});
break;
case 'Aurora Img':
xhr('https://raw.githubusercontent.com/Noitidart/Profilist/%2321/bullet_aurora.png', function(d){saveToDiskAsBuild1(d, win)});
break;
case 'Nightly Img':
xhr('https://raw.githubusercontent.com/Noitidart/Profilist/%2321/bullet_nightly.png', function(d){saveToDiskAsBuild1(d, win)});
break;
default:
win.alert('unknown button clicked, value = "' + targ.value + '"');
}
}
function xhr(url, cb) {
let xhr = Cc["#mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(Ci.nsIXMLHttpRequest);
let handler = ev => {
evf(m => xhr.removeEventListener(m, handler, !1));
switch (ev.type) {
case 'load':
if (xhr.status == 200) {
cb(xhr.response);
break;
}
default:
Services.prompt.alert(null, 'XHR Error', 'Error Fetching Package: ' + xhr.statusText + ' [' + ev.type + ':' + xhr.status + ']');
break;
}
};
let evf = f => ['load', 'error', 'abort'].forEach(f);
evf(m => xhr.addEventListener(m, handler, false));
xhr.mozBackgroundRequest = true;
xhr.open('GET', url, true);
xhr.channel.loadFlags |= Ci.nsIRequest.LOAD_ANONYMOUS | Ci.nsIRequest.LOAD_BYPASS_CACHE | Ci.nsIRequest.INHIBIT_PERSISTENT_CACHING;
xhr.responseType = "arraybuffer"; //dont set it, so it returns string, you dont want arraybuffer. you only want this if your url is to a zip file or some file you want to download and make a nsIArrayBufferInputStream out of it or something
xhr.send(null);
}
function saveToDiskAsBuild1(data, win) {
var file = OS.Path.join(OS.Constants.Path.desktopDir, 'build1.png');
var promised = OS.File.writeAtomic(file, new Uint8Array(data));
promised.then(
function() {
win.alert('succesfully saved image to desktop')
},
function(ex) {
win.alert('FAILED in saving image to desktop')
}
);
}

Resources