I have a solution, which includes 2 projects: main progect, and PlayListFilePlayBackAgent.
in main project, in MainPage a have a listBox, which shows different content. Some content has audioformat, and when i see title, and performer in listbox's item - i click button "play" in this listbox's item. This button make some operations(take track's urls from some server) and than go to page AudioPage. In AudioPage some method make a playlist from those urls, than its saves in isolatedStorage in xml format(serialized).
Here is PlayList.cs
public class Playlist
{
public Playlist()
{
Tracks = new List<PlaylistTrack>();
}
[XmlElement(ElementName = "Track")]
public List<PlaylistTrack> Tracks { set; get; }
[XmlElement(ElementName = "TrackCount")]
public int TrackCount { set; get; }
public static Playlist Load(string filename)
{
Playlist playlist = null;
using (IsolatedStorageFile storage = IsolatedStorageFile.GetUserStoreForApplication())
{
using (IsolatedStorageFileStream stream = storage.OpenFile(filename, FileMode.Open))
{
XmlSerializer xmlSerializer = new XmlSerializer(typeof(Playlist));
playlist = xmlSerializer.Deserialize(stream) as Playlist;
}
}
return playlist;
}
public void Save(string filename)
{
using (IsolatedStorageFile storage = IsolatedStorageFile.GetUserStoreForApplication())
{
using (IsolatedStorageFileStream stream = storage.CreateFile(filename))
{
XmlSerializer xmlSerializer = new XmlSerializer(typeof(Playlist));
xmlSerializer.Serialize(stream, this);
}
}
}
}
And a PlayBackAgent than deserializes this xml into playlist, and plays it.
That's all good, but when i want to listen tracks which were not in those item of listbox i have a problem.
I go back to my listbox, than i select some track and push button "play". In button handler, i .Clear(); my collection, take new urls of new tracks, go to AudioPage and make a new playlist. But in emulator i see the same music i listen before.I thought the problem was in that PlayListFilePlayBackAgent do not update a playlist from isolatedStorade. But when i click different buttons "play" - field TrackCount changes(don't mind to its name, it just says what number of track in playlist must play player), i can see it in output when write Debug.WriteLine(Convert.ToString(playlist.TrackCount)); in AudioPlayer.cs. So what I have: an audioplayer, which plays music only from first playlist I gave to him. When I want to listen another playlist - in AudioPage i see old playlist and can listen music only from those old playlist. What I want: an audioplayer which can change playlist everytime when I push button "play", and can play this music.
PS: I use PlayListFilePlayBackAgent because it can play music even if i close this application. If you need more code - just tell me. Thanks.
Update:
Button handler
private void Audio_Button_Click(object sender, RoutedEventArgs e)
{
string uri = null;
TextBox tb = null;
var grid = (Grid)((Button)sender).Parent;
foreach (var child in grid.Children)
{
if (child is TextBox && (string)((TextBox)child).Tag == "URL")
{
tb = (TextBox)child;
}
}
uri = tb.Text;
BackgroundAudioPlayer.Instance.SkipNext();
//MessageBox.Show(uri);
string url = string.Format("https://api.vk.com/method/audio.getById.xml?audios={0}&access_token={1}", uri, App.AccessToken);
var c = new WebClient();
c.OpenReadCompleted += (sender1, e1) =>
{
XDocument xml = XDocument.Load(e1.Result);
MessageBox.Show(xml.ToString());
var inf = from u in xml.Descendants("audio")
select u;
AudioPage.audios.Clear();
foreach (var item in inf)
{
AudioPage.audios.Add(new AudioAttachment((string)item.Element("url"), (string)item.Element("title"), (string)item.Element("artist")));
}
string destination = string.Format("/AudioPage.xaml");
NavigationService.Navigate(new Uri(destination, UriKind.Relative));
AudioPage.count = 0;
};
c.OpenReadAsync(new Uri(url));
}
OnUseraction.cs
protected override void OnUserAction(BackgroundAudioPlayer player, AudioTrack track, UserAction action, object param)
{
switch (action)
{
case UserAction.Play:
if (player.Track == null)
{
// Load playlist from isolated storage
if (playlist == null)
playlist = Playlist.Load("playlist.xml");
// Debug.WriteLine(playlist.Tracks[0].Title);
Debug.WriteLine(Convert.ToString(playlist.TrackCount));
currentTrack = playlist.TrackCount;
player.Track = playlist.Tracks[currentTrack].ToAudioTrack();
}
else
{
player.Play();
}
break;
case UserAction.Pause:
player.Pause();
break;
case UserAction.SkipNext:
if (currentTrack < playlist.Tracks.Count - 1)
{
currentTrack += 1;
player.Track = playlist.Tracks[currentTrack].ToAudioTrack();
}
else
{
player.Track = null;
}
break;
case UserAction.SkipPrevious:
if (currentTrack > 0)
{
currentTrack -= 1;
player.Track = playlist.Tracks[currentTrack].ToAudioTrack();
}
else
{
player.Track = null;
}
break;
case UserAction.Seek:
player.Position = (TimeSpan)param;
break;
}
NotifyComplete();
}
If you change your playlist in the UI you will need some way of signalling to the agent that it needs to change what it is playing, otherwise it will continue playing the current track.
When user hits the play button, call BackgroundAudioPlayer.Instance.SkipNext from your app, and when you get the OnUserAction event in your agent code you'll be able to recheck your playlist XML file.
if (playlist == null)
playlist = Playlist.Load("playlist.xml");
Where do you set playlist back to Null?
Man, I feel your pain. I have exactly same situation and used exactly the same approach (with the exception that I used IsolatedStorageSettings.ApplicationSettings collection instead of writing into the file).
It works perfectly for the 1st run.
The behavior is really weird.. I saw data updated on the foreground app, but reading same settings from the player would not give me new data. I also tried writing with a new settings key (in case BAP caches results or something) but I got some unstable results where it would work to 2-5 updates and then just stopped seeing new settings keys in the collection. Drives me crazy...
I also noticed that the Tag property of a audio track seems to be copied properly, so I tried to serialize my playlist and pass as a string value through the tag, but got an errors that some memory was not enough or something.. Looks like they have their own restrictions there.
Please let me know if you get find luck with this issue.
Perhaps one of the most detailed info on playing audio on Windows Phone. Check the project here http://developer.nokia.com/community/wiki/Streaming_MP3_player_in_WP7.
However I must say in my case I had to load the Database and extract the information by looping through the Lists without using JSON. Below is the code;
private void LoadPlayListFromIsolatedStorage(BackgroundAudioPlayer player)
{
// clear previous playlist
_playList.Clear();
// access to isolated storage
IsolatedStorageFile isoStore = IsolatedStorageFile.GetUserStoreForApplication();
using (IsolatedStorageFileStream stream = new IsolatedStorageFileStream("AlbumsData.dat", FileMode.OpenOrCreate, isoStore))
{
if (stream.Length > 0)
{
DataContractSerializer serializer = new DataContractSerializer(typeof(List<Album>));
List<Album> strm = serializer.ReadObject(stream) as List<Album>;
List<Song> _songs = strm[0].Songs;
for (int i = 0; i < _songs.Count; i++)
{
AudioTrack audioTrack = new AudioTrack(
new Uri(_songs[i].Directory, UriKind.Absolute), // URL
_songs[i].Title, // MP3 Music Title
"Artist", // MP3 Music Artist
"Album", // MP3 Music Album name
new Uri(_songs[i].AlbumArt, UriKind.Absolute) // MP3 Music Artwork URL
);
_playList.Add(audioTrack);
}
}
else
{
// no AudioTracks from Isolated Storage
}
// start playing
PlayTrack(player);
}
Related
I am currently making a WPF App as a school project. I chose a Media player topic.
I have a slight problem with returning track length of a song and assigning it as a maximum for a slider (I used a slider for song seeking).
/*List<string> pathPesme = new List<string>();
path = od.FileNames;
for (int i = 0; i < path.Length; i++)
{
lista.Add(path[i]);
m.CreateControl();
media = this.m.newMedia(path[i]);*/
//When i want to instance a new class Pesma(song) and put the track length(duzina), it works and returns the value in a mm:ss format.
string ime = System.IO.Path.GetFileName(path[i]);
string duzina = media.durationString;
Pesma pesma = new Pesma(ime, path[i], duzina);
listaPesmi.Add(pesma);
}
However, when i try to do a similar thing and put it as Slider.Maximum, it doesn't work.
foreach (var pesma in dispesma)/**/
{
string aa=iseciString(lbxListaPesama.Items[lbxListaPesama.SelectedIndex].ToString(), ':');
//Method for cutting string to get song name
if (pesma.ImePesme == aa)
{
m.URL = pesma.Path;
premotajSlider.Maximum = Math.Ceiling(m.currentMedia.duration); //Slider: Here i try to put the max value. In the debugger, it returns a 0
m.Ctlcontrols.play();
jacinaZvukaSlider.Value = m.settings.volume;
}
}
It's my first time asking on StackOverflow, so I hope I didn't do something bad. If I did, sorry, still in the learning phase.
I am learning xamarin,
I am trying to refresh my carouselView every time I download data.
The data are downloaded correctly, but the problem is when I download new data and I want to swap on my carrouselView to see my new data after a previous data download.
The carousel 'move' and positionning me at my previous position (index) even if I want to be position on position (index) 0 at every data download
How can I refresh correctly my carouselview ?
Here is my code cs:
try
{
// trying to refresh by renitializing my carouselView
Device.BeginInvokeOnMainThread(() =>
{
NewsList.Position = 0;
NewsList.ItemsSource = null;
});
// Perform the Web request and get the response
HttpWebResponse response = (HttpWebResponse)request.GetResponseAsync().Result;
// Data Downloaded
string json = new StreamReader(response.GetResponseStream()).ReadToEnd();
JObject joResponse = JObject.Parse(json);
JArray MyJsonResult = (JArray)joResponse["value"];
List<NewsModel> newsObj = new List<NewsModel>();
foreach (var j in MyJsonResult)
{
joResponse2 = JObject.Parse(j.ToString());
if (joResponse2["image"]!= null)
{
var news = new NewsModel();
news.NewsName = GetFewWordsSentence(joResponse2["name"].ToString()) ;
news.NewsDate = joResponse2["datePublished"].ToString();
news.NewsImageUrl = joResponse2["image"]["contentUrl"].ToString();
newsObj.Add(news);
// feed my carouselView in the loop for performance
// I have already try, to put this line out of the loop but it is not refresh correctly also
Device.BeginInvokeOnMainThread(() =>
{
NewsList.ItemsSource = newsObj;
});
}
}
}
catch (Exception e)
{
Console.Error.WriteLine(#"ERROR {0}", e.Message);
}
Thanks in advance
The carousel 'move' and positionning me at my previous position (index) even if I want to be position on position (index) 0 at every data download
According to your description, you download data and display CarouselView, then you swap item for CarouselView in any position. You want to display new data and go to position 0 when download data again, am I right?
If yes, I suggest you can use observablecollection to replace List firstly, it implement INotifyPropertychanged interface to notify when data update.
public ObservableCollection<NewsModel> newsObj { get; set; }
then clear data when you download data again,
newsObj.Clear();
Using ScrollTo scrolls the item at the specified index into view.
Device.BeginInvokeOnMainThread(()=>
{
newsList.ScrollTo(0);
}
) ;
My MediaPlaybackList.ShuffledItems has 10 items in it. But when I was trying to convert the items back to a list of ViewModel (in my case it is Music) using the uri, I got null.
Why is that? Is it because I load the file from my local drive?
This is how I get the uri:
public static async Task<List<Music>> GetRealPlayList()
{
if (PlayList.ShuffleEnabled)
{
if (ShuffledPlayList.Count == 0)
{
foreach (var music in PlayList.ShuffledItems)
{
ShuffledPlayList.Add(await Music.GetMusic(music.Source.Uri.AbsolutePath));
}
}
return ShuffledPlayList;
}
else
return CurrentPlayList;
}
This is how I set the items:
public static async Task SetPlayList(IEnumerable<Music> playlist)
{
if (Helper.SamePlayList(playlist, CurrentPlayList)) return;
PlayList.Items.Clear();
CurrentPlayList.Clear();
foreach (var music in playlist)
{
var item = new MediaPlaybackItem(MediaSource.CreateFromStorageFile(await Helper.CurrentFolder.GetFileAsync(music.GetShortPath())));
PlayList.Items.Add(item);
CurrentPlayList.Add(music);
}
}
What ways else can I convert the MediaPlackBackItem back to the ViewModel? The GetDisplayProperties().MusicProperties doesn't have the some properties that I want and the properties in it are also empty.
When you create MediaSource,you can set CustomProperties to save the file path in it.And when you loop through the PlayList.ShuffledItems,get file path from the CustomProperties.
Set the items:
MediaSource source = MediaSource.CreateFromStorageFile(await Helper.CurrentFolder.GetFileAsync(music.GetShortPath()));
source.CustomProperties.Add("Path", file.Path);
var item = new MediaPlaybackItem(source);
Get Music class:
foreach (var music in PlayList.ShuffledItems)
{
MediaSource source = music.Source;
String path = sour.CustomProperties["Path"].ToString();
ShuffledPlayList.Add(await Music.GetMusic(path));
}
DataObject is part of System.Windows and not available in Xamarin.Mac. So how can I do a workaround. I want to create a drag n drop function with a external file. On Windows I can do this with a DataObject.
I already searched for alternatives, tried it with NSUrl but without success.
Drag into your app:
All NSView-based classes have a RegisterForDraggedTypes method that you pass what you want to accept (in your case a UTType.FileURL as you are passing something into the app, assumably a file from Finder).
So in this case I have a NSTableView that accepts files, adds them the the table and fires an notification that causes an NSTask to process them (it is a FFMPEG-based task).
tableView.RegisterForDraggedTypes(new string[] { UTType.FileURL };
From there it depends upon the type of NSView you are dragging into, but for example with an NSTableView, you assign its data delegate a NSTableViewDataSource subclass which you have overridden ValidateDrop and AcceptDrop.
In NSTableViewDataSource.ValidateDrop you confirm if the drop contains file(s) that you will accept. In this case, as long as it is a file of any type I accept:
public override NSDragOperation ValidateDrop(NSTableView tableView, NSDraggingInfo info, nint row, NSTableViewDropOperation dropOperation)
{
var operation = NSDragOperation.Copy;
using (var pasteBoard = info.DraggingPasteboard)
{
foreach (var item in pasteBoard.PasteboardItems)
{
if (!item.Types.Contains(UTType.FileURL))
{
operation = NSDragOperation.None;
}
item.Dispose();
}
}
return operation;
}
In in NSTableViewDataSource.AcceptDrop, you actually process the files from the drop.
public override bool AcceptDrop(NSTableView tableView, NSDraggingInfo info, nint row, NSTableViewDropOperation dropOperation)
{
using (var pasteBoard = info.DraggingPasteboard)
{
if (pasteBoard.PasteboardItems.Length > 0)
{
var range = new NSRange(-1, 0);
foreach (var item in pasteBoard.PasteboardItems)
{
if (item.Types.Contains(UTType.FileURL))
{
var finderNode = item.GetStringForType(UTType.FileURL);
// you have a file from macOS' finder, do something with it, assumable in a table view you would add a record/row....
var url = NSUrl.FromString(finderNode);
// url has the file extension, filename, full path, etc...
Post a notification / Add a task to GCD / etc...
}
item.Dispose();
}
return true;
}
}
return false;
}
Drag out of your app:
Lets assume you have an NSView subclass that you wish to drag a "file" out of and into Finder or any app that accepts file drops. Implement the interfaces; INSDraggingSource and INSPasteboardItemDataProvider on your NSView subclass.
In the MouseDown event, start your drag for UTType.FileURL types:
public override void MouseDown(NSEvent theEvent)
{
var pasteboardItem = new NSPasteboardItem();
pasteboardItem.SetDataProviderForTypes(this, new string[1] { UTType.FileURL });
var draggingItem = new NSDraggingItem(pasteboardItem);
var fileDragIcon = new NSImage("theDraggingIcon.png");
draggingItem.SetDraggingFrame(new CoreGraphics.CGRect(0,0,40,40), fileDragIcon);
BeginDraggingSession(new NSDraggingItem[1] { draggingItem }, theEvent, this);
}
In the ProvideDataForType place the file url onto the pasteboard:
public void ProvideDataForType(NSPasteboard pasteboard, NSPasteboardItem item, string type)
{
if (type == UTType.FileURL )
{
var url = new NSUrl("/Users/Sushi/Desktop/StackOverflow.png", false);
url.WriteToPasteboard(pasteboard);
}
}
Note: That is just one way to transfer a file, there are three other file drag transfers, you can also provide an array of urls, a promise to create the file yourself later and actually inserting the file's data into the pasteboard.
Im building a digital signage application. I need to show a mix of images and videos (3 images and then a video - then repeat this). I am using a WPF application for this. I ran out of luck using the "MediaElement" in WPF - I had some files that would not play (even the default "wildlife.wmv" file in some situations). I turned to VLC and now my application only runs for ~3 hours before i run out of memory / my vlc player going black.
I have a VLC component wrapped inside a Windows Forms component. The Windows Forms component is then added to my WPF application.
My code is shown below. Im loading this using reflection - I found this using the least amount of memory. Been going about with this for hours.
//code
string path = Assembly.GetExecutingAssembly().Location;
string directory = new System.IO.FileInfo(path).Directory.FullName;
string newPath = System.IO.Path.Combine(directory, "MedianVLCLibrary.dll");
if (System.IO.File.Exists(newPath))
{
Assembly vlcAssembly = Assembly.LoadFrom(newPath);
myVlcType = vlcAssembly.GetType("MedianVLCLibrary.VLCUserControl");
}
else
{
MedianLog.Log.Instance.LogFatal("Could not fild MedianVLCLibrary.dll");
throw new FileNotFoundException(newPath);
}
obj = Activator.CreateInstance(myVlcType);
this.presentationGrid.Children.Add((UIElement)obj); //adding the ui element to the WPF grid
MethodInfo playMethod = myVlcType.GetMethod("Play");
playMethod.Invoke(obj, new object[] { file });
EventInfo completedEvent = myVlcType.GetEvent("PlayCompleted");
Delegate completedDelegate = Delegate.CreateDelegate(completedEvent.EventHandlerType, this, "PlayerCompleted");
completedEvent.AddEventHandler(obj, completedDelegate);
And then im doing my cleanup in my "PlayComplete" method before im invoking a callback method.
obj = null;
myVlcType = null;
vlcAssembly = null;
this.presentationGrid.Children.Clear();
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
FinishedCallback();
I have made a wrapper around VLC, using samples found online. Please see code below.
public partial class VLCUserControl : UserControl, IDisposable
{
AxVLCPlugin2 vlc;
public VLCUserControl()
{
InitializeComponent();
vlc = new AxVLCPlugin2();
vlc.BeginInit();
windowsFormsHost.Child = vlc;
vlc.EndInit();
}
public void Play(string path)
{
var uri = new Uri(path);
var convertedURI = uri.AbsoluteUri;
vlc.playlist.add(convertedURI, null, null);
vlc.playlist.play();
vlc.MediaPlayerEndReached += Vlc_MediaPlayerEndReached;
}
private void Vlc_MediaPlayerEndReached(object sender, EventArgs e)
{
vlc.playlist.items.clear();
vlc.MediaPlayerEndReached -= Vlc_MediaPlayerEndReached;
if (PlayCompleted != null)
{
PlayCompleted();
}
//vlc = null;
GC.Collect();
}
public void Dispose()
{
vlc.Dispose();
}
public event Action PlayCompleted;
}