Click here to Skip to main content
15,883,705 members
Articles / Mobile Apps / Windows Mobile

Audio Book Player

Rate me:
Please Sign up or sign in to vote.
4.86/5 (36 votes)
11 Jun 2009CPOL6 min read 197.5K   3.5K   84  
Audio player designed specifically for listening to audio books
using System;
using System.Collections.Generic;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Collections;
using System.Threading;
using System.IO;
using System.Reflection;
using Microsoft.Win32;

namespace abPlayer
{
    public partial class frmPlayer : Form
    {
        // API used
        [DllImport("coredll.dll")]
        public static extern IntPtr GetDC(IntPtr hWnd);
        [DllImport("coredll.dll")]
        public static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);
        // skin related constants
        private const String SKIN_DIR = "\\skins";
        private const String NO_SKIN = "None";
        private String SkinDir;
        // default backlight timeout
        private const int BACKLIGHT_TIMEOUT = 10;
        // key press constants
        const int TIME_CHANGE = 10;
        const int VOLUME_CHANGE = 5;
        // class that encapsulates various calls to win API
        private cASUS639DeviceSpecifics DeviceSpecifics = null;
        // form used to manage and maintain books (playlists)
        private frmLibrary Library = null;
        // form used to manage book properties
        private frmProps Props = null;
        // the class that manages the windows media player access
        private cPlayer Player = null;
        // In order to prevent auto power off 
        // a set of API needs to be called periodically (less than the auto
        // power off timeout set in the device. So - 
        // we keep the interval desired (Threshold) and a time stamp indicating
        // when the reset was last performed
        private long KeepPowerThreshold = 30;
        private DateTime KeepPowerTimeStamp = DateTime.Now;
        // text color of the display and the forecolor of the controls change
        // to reflect battery state: charging = white, good = yellow
        // medium = orange, critical = red.
        private Color TextColor = Color.White;
        // since volume is saved as a book property and is set as a book is being
        // played, we keep the volume level at entry time and resores it on exit.
        private int SaveEntryVolume = 0;
        // same for backlight timeout
        private int SaveEntryBacklight = 0;
        // sleep function data
        private DateTime SleepTime = DateTime.Now;
        private bool SleepActive = false;
        private int[] SleepIntervals = { 15, 30, 60, 90, 120 };
        // media transition goes through several statuses
        // to avoid intermediate status displays, status paint is not done
        // in real time but rather on the next timer tick
        private bool NeedStatusPaint = false;
        // if no media is being played timeline location does't change
        // to avoid excessive and unnecessary screen paint and persistancy saves
        // we only do it if location actually changed
        private int LastLocationSaved = -1;

        public frmPlayer()
        {
            InitializeComponent();
            // this will catch any object and subsequent object creation error (as
            // all 7 major objects - 4 forms and 3 classes are instantiated here).
            // most common reasons: low memory and low battery
            try
            {
                // various non managed API calls
                DeviceSpecifics = new cASUS639DeviceSpecifics();
                // forms used to manage and maintain books & book properties
                Library = new frmLibrary();
                // the class that manages the windows media player access
                Player = new cPlayer();
                // form to manage book properties
                Props = new frmProps(Library, Player, DeviceSpecifics);
            }
            catch (Exception e)
            {
                MessageBox.Show("Failed to Initialize Objects - " +
                    e.Message, "Fatal Error");
                Close();
                return;
            }
            // align buttons
            picDark.Left = picLight.Left = 2;
            // prepare paint resources
            apiDC = GetDC(this.Handle);
            if (apiDC == IntPtr.Zero)
            {
                MessageBox.Show("Failed to Initialize GDI Resource", "Fatal Error");
                Close();
                return;
            }
            try
            {
                screenGraphics = Graphics.FromHdc(apiDC);
                fgBMP = new Bitmap(Width, pnlBottom.Top - pnlTop.Height);
                fgGraphics = Graphics.FromImage(fgBMP);
                bgBMP = new Bitmap(Width, pnlBottom.Top - pnlTop.Height);
                bgGraphics = Graphics.FromImage(bgBMP);
            }
            catch
            {
                MessageBox.Show("Failed to Initialize Graphics Resource", "Fatal Error");
                Close();
                return;
            }
            todFont = new Font(this.Font.Name, TOD_FONT_SIZE, FontStyle.Bold);
            // enumerate existing skins and mark active one
            SkinDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().GetName().CodeBase) +
                SKIN_DIR;
            String sk = Library.BookStore.Skin;
            try
            {
                foreach (DirectoryInfo dir in new DirectoryInfo(SkinDir).GetDirectories())
                {
                    MenuItem mnu = new MenuItem();
                    mnu.Text = dir.Name;
                    mnu.Checked = (mnu.Text.ToUpper() == sk.ToUpper());
                    mnu.Click += new EventHandler(mnuSkin_Click);
                    mnuSkins.MenuItems.Add(mnu);
                }
            }
            catch
            {
                MessageBox.Show("Failed to Find Skins Dir", "Error");
            }
            MenuItem mni = new MenuItem();
            mni.Text = "-";
            mnuSkins.MenuItems.Add(mni);
            mni = new MenuItem();
            mni.Text = NO_SKIN;
            mni.Checked = (mni.Text.ToUpper() == sk.ToUpper());
            mni.Click += new EventHandler(mnuSkin_Click);
            mnuSkins.MenuItems.Add(mni);
            //Load Active skin
            LoadSkin(sk);
            // ser print related sizes
            textHeight = (int)fgGraphics.MeasureString("Z", this.Font).Height;
            // time till sleep display
            lblSleep.Text = "";
            // set backlight timeout 
            SaveEntryBacklight = DeviceSpecifics.BacklightTimeout;
            if (BACKLIGHT_TIMEOUT != SaveEntryBacklight)
                DeviceSpecifics.BacklightTimeout = BACKLIGHT_TIMEOUT;
            // initialize event handlers
            Player.PlayItemChanged += new PlayItemChangedEventHandler(Player_PlayItemChanged);
            Player.StatusChanged += new StatusChangedEventHandler(Player_StatusChanged);
            SaveEntryVolume = DeviceSpecifics.Volume;
            // get device screensave timeout 
            KeepPowerThreshold = DeviceSpecifics.PowerOffThreshold;
            // if we have an active book - go load it
            if ((Library.BookStore.ActiveBookName.Length > 0) &&
                (Library.BookStore.GetBookFileCount("") > 0))
            {
                // set volume
                DeviceSpecifics.Volume = Library.BookStore.GetBookProperty(eBookProperty.VOLUME, "");
                // Load playlist, restore bookmark and pause
                Player.Playlist(Library.BookStore.GetBookFiles(""),
                                Library.BookStore.GetBookProperty(eBookProperty.BOOKMARK_ENTRY, ""),
                                Library.BookStore.GetBookProperty(eBookProperty.BOOKMARK_LOCATION, ""),
                                (Library.BookStore.GetBookProperty(eBookProperty.SHUFFLE, "") != 0));
                // take stock of current location
                PaintLocation();
                // force garbage collection here to dispose of all obsolete objects 
                // to prevent gc to kick in when it may disturb the playing
                GC.Collect();
                // signal ready to play
                DeviceSpecifics.Beep();
            }
            tmr.Enabled = true;
        }
        // skin menu clicked
        void mnuSkin_Click(object sender, EventArgs e)
        {
            if (((MenuItem)sender).Checked) return;
            foreach (MenuItem mnu in mnuSkins.MenuItems)
                mnu.Checked = (((MenuItem)sender) == mnu);
            Library.BookStore.Skin = ((MenuItem)sender).Text;
            LoadSkin(((MenuItem)sender).Text);
            PaintScreen();
        }
        // load 5 forms skins
        void LoadSkin(String skin)
        {
            ApplySkin(skin, this);
            ApplySkin(skin, Library);
            ApplySkin(skin, Library.FileSelector);
            ApplySkin(skin, Props);
            ApplySkin(skin, Props.MediaProps);
            // set text color of sleep label to picture color
            if (skin == NO_SKIN)
                lblSleep.ForeColor = TextColor;
            else
            {
                try
                {
                    Bitmap bmp = new Bitmap(picSleep.Image);
                    for (int i = 0; i < picSleep.Width; i++)
                        for (int j = 0; j < picSleep.Height; j++)
                        {
                            int c = bmp.GetPixel(i, j).ToArgb();
                            if (((c & 0x00FF0000) > (0x00FF0000 / 2)) ||
                            ((c & 0x0000FF00) > (0x0000FF00 / 2)) ||
                            ((c & 0x000000FF) > (0x000000FF / 2)))
                            {
                                lblSleep.ForeColor = bmp.GetPixel(i, j);
                                bmp.Dispose();
                                return;
                            }
                        }
                }
                catch { }
            }
        }
        // find all picturebox controls and load image
        void ApplySkin(String skin, Control ctrl)
        {
            foreach (Control item in ctrl.Controls)
                if (item.GetType() == typeof(PictureBox))
                {
                    if (((PictureBox)item).Tag != null)
                    {
                        ((PictureBox)item).Paint += Library.FileSelector.Pic_Paint;
                        ((PictureBox)item).Image = null;
                        // load a picture
                        if (skin != NO_SKIN)
                        {
                            try
                            {
                                String[] file = Directory.GetFiles(SkinDir + "\\" + skin, (String)((PictureBox)item).Tag + ".*");
                                if (file.Length > 0)
                                    ((PictureBox)item).Image = new Bitmap(file[0]);
                            }
                            catch { }
                        }
                    }
                }
                else
                    ApplySkin(skin, item);
        }
        // monitor status change and show it
        // do it on next timer 1 second tick so if status is changed rapidly
        // (as in playlist loading) I don't show all intermediate statuses
        void Player_StatusChanged(object sender, StatusChangedEventArgs e)
        {
            NeedStatusPaint = true;
        }
        // player or user changed the active file. Commit it to persistant storage
        void Player_PlayItemChanged(object sender, PlayItemChangedEventArgs e)
        {
            if (e.ItemIndex > -1)
                Library.BookStore.SetBookProperty(eBookProperty.BOOKMARK_ENTRY, e.ItemIndex, "");
            PaintScreen();
        }
        // this is a timer (1 second) based event so we can reflect 
        // the new timeline marker visually and commit it to persistant storage
        // we also do all time based housekeeping
        private void tmr_Tick(object sender, EventArgs e)
        {
            if ((Player.Duration != -1) &&
                (Player.Location != -1) &&
                (LastLocationSaved != Player.Location))
            {
                LastLocationSaved = Player.Location;
                PaintLocation();
                Library.BookStore.SetBookProperty(eBookProperty.BOOKMARK_LOCATION,
                    Player.Location, "");
            }
            if (NeedStatusPaint)
            {
                PaintStatus(Player.StatusString);
                NeedStatusPaint = false;
            }
            DateTime now = DateTime.Now;
            // check if time since last reset has reached the threshold
            if ((now - KeepPowerTimeStamp).TotalSeconds >= KeepPowerThreshold)
            {
                // yes - rewind timestamp and paint time and battery state
                KeepPowerTimeStamp = now;
                DeviceSpecifics.PreventAutoPowerOff();
            }
            // update tod display
            if(now.Second == 0)
                PaintBatteryVolAndTOD();
            // flying text
            if ((Player.PlaylistCount > 0) &&
                (flyingTextCount > 0))
                PaintFlyingText();
            // handle sleep
            if (SleepActive)
            {
                DrawSleepTime();
                if (SleepTime <= DateTime.Now)
                {
                    ReleaseResources();
                    // shutdown
                    DeviceSpecifics.PowerOff();
                    Close();
                    return;
                }
            }
        }
        // click
        private void frmPlayer_Click(object sender, EventArgs e)
        {
            SwitchState();
        }
        // exit program after stopping media player
        private void picExit_Click(object sender, EventArgs e)
        {
            Library.FileSelector.FlashControl((Control)sender);
            ReleaseResources();
            Close();
        }
        // release resources. close all
        void ReleaseResources()
        {
            try
            {
                tmr.Enabled = false;
                Player.StopAndClear();
                if (fgBMP != null)
                    fgBMP.Dispose();
                if (fgGraphics != null)
                    fgGraphics.Dispose();
                if (bgBMP != null)
                    bgBMP.Dispose();
                if (bgGraphics != null)
                    bgGraphics.Dispose();
                if (screenGraphics != null)
                    screenGraphics.Dispose();
                if (apiDC != IntPtr.Zero)
                    ReleaseDC(this.Handle, apiDC);
                Player.Dispose();
            }
            catch { }
            finally
            {
                DeviceSpecifics.Volume = SaveEntryVolume;
                DeviceSpecifics.BacklightTimeout = SaveEntryBacklight;
            }
        }
        // light on
        private void picLight_Click(object sender, EventArgs e)
        {
            Library.FileSelector.FlashControl((Control)sender);
            picLight.Visible = !picLight.Visible;
            picDark.Visible = !picDark.Visible;
            DeviceSpecifics.BacklightTimeout = BACKLIGHT_TIMEOUT;
        }
        // light off
        private void picDark_Click(object sender, EventArgs e)
        {
            Library.FileSelector.FlashControl((Control)sender);
            picLight.Visible = !picLight.Visible;
            picDark.Visible = !picDark.Visible;
            DeviceSpecifics.BacklightTimeout = 0;
        }
        // play button pressed
        private void picPlay_Click(object sender, EventArgs e)
        {
            // if we are not already playing and there's a playlist - do it
            Library.FileSelector.FlashControl((Control)sender);
            if((Player.Status != ePlayerStatus.PLAYING) && (Player.PlaylistCount > 0))
                Player.Play();
        }
        // stop button pressed
        private void picStop_Click(object sender, EventArgs e)
        {
            Library.FileSelector.FlashControl((Control)sender);
            Player.Stop();
        }

		public delegate bool PlayerAction();

        // next button pressed
        private void picNext_Click(object sender, EventArgs e)
        {
            Library.FileSelector.FlashControl((Control)sender);
            circumventTransitioningHang(new PlayerAction(Player.Next));
        }
        // prev button pressed
        private void picPrev_Click(object sender, EventArgs e)
        {
            Library.FileSelector.FlashControl((Control)sender);
            circumventTransitioningHang(new PlayerAction(Player.Previous));
        }
        // sometimes the player gets stuck in transitioning state
        // the only thing that gets it going again is play()
        // this doesn't happen if player is already in play state (only paused/stopped)
        void circumventTransitioningHang(PlayerAction action)
        {
            //bool needStop = false;
            //try
            //{
            //    if (Player.Status != ePlayerStatus.PLAYING)
            //    {
            //        needStop = true;
            //        Player.Play();
            //        Player.WaitForPlayerStatus(ePlayerStatus.PLAYING);
            //    }
                action();
            //    if (needStop)
            //    {
            //        Player.WaitForPlayerStatus(ePlayerStatus.PLAYING);
            //        Player.Stop();
            //    }
            //}
            //catch { }
        }
        // sleep presed
        private void picSleep_Click(object sender, EventArgs e)
        {
            // convert sleeptime to minutes from now
            foreach (int minutesInterval in SleepIntervals)
            {
                // allow 5 secs to jump between intervals
                if ((SleepTime.AddSeconds(5).Subtract(DateTime.Now)).TotalMinutes < 
                    minutesInterval)
                {
                    SleepTime = DateTime.Now.AddMinutes(minutesInterval);
                    SleepActive = true;
                    lblSleep.Visible = true;
                    DrawSleepTime();
                    return;
                }
            }
            SleepTime = DateTime.Now;
            SleepActive = false;
            lblSleep.Visible = false;
            DrawSleepTime();
        }
        // display sleep time
        void DrawSleepTime()
        {
            if (SleepActive)
                lblSleep.Text = "Sleep in: " +
                    (SleepTime.Subtract(DateTime.Now)).ToString();
            else 
                lblSleep.Text = "";
        }
        // try to pause before popup
        private void cnm_Popup(object sender, EventArgs e)
        {
            if (Player.Status == ePlayerStatus.PLAYING)
            {
                Player.Pause();
                Player.WaitForPlayerStatus(ePlayerStatus.PAUSED);
            }
        }
        // paint screen
        private void abPlayer_Paint(object sender, PaintEventArgs e)
        {
            PaintScreen(e.Graphics);
        }
        // properties button clicked
        private void picProps_Click(object sender, EventArgs e)
        {
            Library.FileSelector.FlashControl((Control)sender);
            // only if we have a playlist
            if (Player.PlaylistCount <= 0) return;
            // if we are in a play state - try to pause
            if (Player.Status == ePlayerStatus.PLAYING)
            {
                Player.Pause();
                Player.WaitForPlayerStatus(ePlayerStatus.PAUSED);
            }
            // if we failed (or state is something else - deny access)
            if ((Player.Status != ePlayerStatus.PAUSED) &&
                (Player.Status != ePlayerStatus.STOPPED)) return;
            // set all property values to their current state
            // make properties form accessible
            DeviceSpecifics.BacklightTimeout = 0;
            if (Props.ShowDialog() == DialogResult.OK)
            {
                if ((Props.Dirty) && (Player.Status == ePlayerStatus.PAUSED))
                {
                    if (Props.hscVol.Value != Library.BookStore.GetBookProperty(eBookProperty.VOLUME, ""))
                    {
                        Library.BookStore.SetBookProperty(eBookProperty.VOLUME, Props.hscVol.Value, "");
                        DeviceSpecifics.Volume = Props.hscVol.Value;
                    }
                    // shuffle option changed
                    if (Props.chkShuffle.Checked != (Library.BookStore.GetBookProperty(eBookProperty.SHUFFLE, "") != 0))
                    {
                        Library.BookStore.SetBookProperty(eBookProperty.SHUFFLE, (Props.chkShuffle.Checked ? 1 : 0), "");
                        Player.Shuffle = Props.chkShuffle.Checked;
                    }
                    // playlist entry  
                    if ((Props.hscEntry.Value - 1) != Library.BookStore.GetBookProperty(eBookProperty.BOOKMARK_ENTRY, ""))
                    {
                        Library.BookStore.SetBookProperty(eBookProperty.BOOKMARK_ENTRY, Props.hscEntry.Value - 1, "");
                        Library.BookStore.SetBookProperty(eBookProperty.BOOKMARK_LOCATION, Props.hscTime.Value, "");
                        Player.Playlist(null,
                                Props.hscEntry.Value - 1,
                                Props.hscTime.Value,
                                Props.chkShuffle.Checked);
                        PaintScreen();
                    }
                    else
                    // timeline marker changed 
                    if (Props.hscTime.Value != Library.BookStore.GetBookProperty(eBookProperty.BOOKMARK_LOCATION, ""))
                    {
                        Library.BookStore.SetBookProperty(eBookProperty.BOOKMARK_LOCATION, Props.hscTime.Value, "");
                        Player.Location = Props.hscTime.Value;
                    }
                    //indicate change done & we are ready to play
                    DeviceSpecifics.Beep();
                }
            }
            if(picDark.Visible)
                DeviceSpecifics.BacklightTimeout = BACKLIGHT_TIMEOUT;
        }
        // library button clicked
        private void picLibrary_Click(object sender, EventArgs e)
        {
            Library.FileSelector.FlashControl((Control)sender);
            // if we are in a playing state - try to pause
            if (Player.Status == ePlayerStatus.PLAYING)
            {
                Player.Pause();
                if (!Player.WaitForPlayerStatus(ePlayerStatus.PAUSED))
                    return;
            }
            // show library form and:
            DeviceSpecifics.BacklightTimeout = 0;
            if ((Library.ShowDialog() == DialogResult.OK) ||
                ((Player.PlaylistCount > 0) &&
                (Player.PlaylistCount != Library.BookStore.GetBookFileCount(""))))
            {
                // we came back after user pressed play
                tmr.Enabled = false;
                PaintScreen();
                Application.DoEvents();
                // reset current playlist
                Player.StopAndClear();
                // set volume
                Props.hscVol.Value = Library.BookStore.GetBookProperty(eBookProperty.VOLUME, "");
                Props.lblVol.Text = "Volume: " + Props.hscVol.Value.ToString();
                DeviceSpecifics.Volume = Props.hscVol.Value;
                // load playlist and start playing it from the last position
                Player.Playlist(Library.BookStore.GetBookFiles(""),
                                Library.BookStore.GetBookProperty(eBookProperty.BOOKMARK_ENTRY, ""),
                                Library.BookStore.GetBookProperty(eBookProperty.BOOKMARK_LOCATION, ""),
                                (Library.BookStore.GetBookProperty(eBookProperty.SHUFFLE, "") != 0));
                GC.Collect();
                DeviceSpecifics.Beep();
                tmr.Enabled = true;
                PaintScreen();
            }
            else
            {
                // we came back after user pressed the cancel button
                // if active book was deleted - reset player
                if (Library.BookStore.ActiveBookName == "")
                    Player.StopAndClear();
                PaintScreen();
            }
            if (picDark.Visible)
                DeviceSpecifics.BacklightTimeout = BACKLIGHT_TIMEOUT;
        }
        // get battery life percent and according to its level set the
        // display color: charging=white, good=yellow, medium=orange, low=red
        private int GetBatteryLifePercentAndSetTextColor()
        {
            PowerState state = DeviceSpecifics.PowerAndBatteryState;
            Color c;
            if (state.ACLineConnected)
                c = Color.White;
            else
                c = (state.BatteryLifePercent <= 35) ? Color.Red :
                    (state.BatteryLifePercent <= 70) ? Color.Orange : Color.Yellow;
            if (c != TextColor)
            {
                TextColor = c;
                PaintScreen();
            }
            return state.BatteryLifePercent;
        }
        // key press function
        private void frmPlayer_KeyDown(object sender, KeyEventArgs e)
        {
            //if (Player.PlaylistCount <= 0) return;
            switch (e.KeyCode)
            {
                case Keys.Right:
                    AdjustLocation(TIME_CHANGE);
                    break;
                case Keys.Left:
                    AdjustLocation(-TIME_CHANGE);
                    break;
                case Keys.Up:
                    AdjustVolume(-VOLUME_CHANGE);
                    break;
                case Keys.Down:
                    AdjustVolume(VOLUME_CHANGE);
                    break;
                case Keys.Enter:
                    SwitchState();
                    break;
            }
            e.Handled = true;
        }
        // volume manipulation function
        private void AdjustVolume(int delta)
        {
            int vol = DeviceSpecifics.Volume;
            int newVol = ((vol + delta) < 0) ? 0 :
                ((vol + delta) > 100) ? 100 :
                (vol + delta);
            if (vol != newVol)
            {
                DeviceSpecifics.Volume = newVol;
                Library.BookStore.SetBookProperty(eBookProperty.VOLUME, newVol, "");
                PaintBatteryVolAndTOD();
            }
        }
        // location manipulation function
        private void AdjustLocation(int delta)
        {
            int loc = Player.Location;
            int dur = Player.Duration;
            if ((loc != -1) &&
                (dur != -1))
            {
                int newLoc = ((loc + delta) < 0) ? 0 :
                    ((loc + delta) > dur) ? dur :
                    (loc + delta);
                if (newLoc != loc)
                {
                    Player.Location = newLoc; 
                    Library.BookStore.SetBookProperty(eBookProperty.BOOKMARK_LOCATION,
                        newLoc, "");
                    PaintLocation();
                }
            }
        }
        // switch between play and pause
        private void SwitchState()
        {
            if (Player.PlaylistCount > 0)
            {
                if (Player.Status == ePlayerStatus.PLAYING)
                    Player.Pause();
                else
                    Player.Play();
            }
        }
        // show about dialog
        private void mnuAbout_Click(object sender, EventArgs e)
        {
            DeviceSpecifics.BacklightTimeout = 0;
            frmAbout frm = new frmAbout();
            frm.Left = Width / 2 - frm.Width / 2;
            frm.Top = Height / 2 - frm.Height / 2;
            frm.ShowDialog();
            if (picDark.Visible)
                DeviceSpecifics.BacklightTimeout = BACKLIGHT_TIMEOUT;
            PaintScreen();
        }
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Israel Israel
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions