Mp3File - Třída pro práci s mp3 souborem

Při hledání informací o přehrávání mp3 souborů na .NET platformě jsem narazil na pár neúplných informací, na malé kousky kódu a vůbec na přiklady, které ani trochu nesplňovaly moji představu o třídě, která by měla zajišťovat základní funkcionalitu přehrávání mp3 souborů.

V článku budu popisovat jednotlivé kroky podrobněji a uvádět větší množství odkazů do dokumentace, protože předpokládám čtenáře začatečníka.

Moc dobře si uvědomuji, že pokud zrovna neprogramujete hudební přehrávač je použití mojí třídy omezené, ale třídu jsem koncipoval tak, aby se dala jednoduše použít do každého projektu, kde je např. potřeba zvuk pro upomínky nebo zkrátka použít libolný zvuk. 

Motivace 

Hned na úvod bych rád zmínil pár vlastností, které jsem si na začátku stanovil:

  • Třída musí být velmi jednoduše a rychle použitelná
  • Vše v jednom souboru, který se dá jednoduše přidat do projektu
  • Musí svými činnostmi vyvolávat události
  • K dispozici by měla informace o skladbě (ID3) nezávisle na instanci hlavní třídy a měla by jít zapisovat
  • Všechny metody dobře okomentované, aby byla jasná jejich funkčnost a přehledně rozčleněn kód

Ano, tyhle vlastnosti by měla splňovat každá napsaná třída, ale je s podivem kolik jich je takových, které moje požadavky vůbec nesplňují.

Základní princip

Operační systém Windows je sám o sobě schopen přehrát mp3 soubor a to přes Windows Media system (winmm.dll), obsahují funkci mciSendString, pomocí které lze posílat příkazy do multimediálních zařízení a získávat na ně odpovědi. Funkce má následující prototyp:

MCIERROR mciSendString(
LPCTSTR lpszCommand, 
LPTSTR lpszReturnString, 
UINT cchReturn,      
HANDLE hwndCallback  
);

lpszCommand - je vstupní řetězec s příkazem
lpszReturnString - je výstupní řetězec
cchReturn - je maximální délka řetězce

Ostatní parametry nepoužívám.

Pro import této funkce použijeme atribut DllImport z namespace System.Runtime.InteropServices.

[DllImport("winmm.dll")]
private static extern long mciSendString(
string lpstrCommand, 
StringBuilder lpstrReturnString, 
int uReturnLength, 
int hwndCallback);

Tímto si zpřístupníme tuto funkci v managed kódu. Ještě pro malé upřesnění: Pro návratovou hodnotu, se používá třída StringBuilder, která se většinou používá tam, kde by se v C++ použil ukazatel na řetězec.

Příkazy pro ovládání přehrávání

Celkový souhrn příkazů, které funkce mciSendString podporuje je dispozici v tomto seznamu. Každý příkaz je vázán na tzv. alias - textový jednoznačný identifikátor právě přehrávaného souboru. Ve všech příkladech, které jsem na internetu našel se vždy používal stejný indentifikátor pro všechny instance tříd pro přehrávání. Moje řešení je trochu více univerzální. Používám alias, který je jednoznačný pouze pro aktuální instanci mojí třidy Mp3File. Tím je zajišteno možnost přehrávat a pracovat s více soubory současně.

Abych dosáhl jednoznačnosti aliasu pro každou instanci třídy, použil jsem Guid. Inicializace členské proměnné vypadá takto:

private string m_Alias = Guid.NewGuid().ToString();

Pojďme si nyní probrat jednotlivé příkazy, které se posílají do výše zmíněné funkce mciSendString a které ve své třídě používám. Popis bude jen k těm důležitejším, protože ostatní si ani nezaslouží komentář.

open "[název souboru]" type mpegvideo alias [alias] 

Otevře soubor a přiřadí mu uvedený alias.

status [alias] mode

Dotaz na stav souboru. V tomto připadě funkce vrací stavy "stopped", "playing", "paused" a nebo prázdný řetězec, pokud alias neexistuje.

status [alias] length
status [alias] position

Vrací celkovou délku souboru v aktuální pozici v souboru v milisekundách.

seek [alias] to [čas v milisekundách]

Je příkaz, který nastaví soubor na danou pozici. Má ale jednu nevýhodu. Nastaví stav souboru na "stopped", což není žádoucí. Většinou chceme po nastavení pozice pokračovat v přehrávání nebo zůstat v pozastaveném stavu. Tento fakt jsem vyřešil ve své třídě, takže při práci s ní nic nepoznáte.

close [alias]
play [alias]
stop [alias]
pause [alias]
resume [alias]

To jsem ony příkazy, které komentář nepotřebují. Ani tento komentář. :)

Windows Media system má k dispozici hodně příkazů, které by ještě mohli eventuelně funkčnost třídy rozšířit. Myslím, že tato základní verze bude většině vyhovovat.

Interface a použití třídy

Myslím si, že nejen použití, ale i interface třidy se dá nejlépe ukázat na příkladu, takže když dovolíte...

// Vytvoříme novou instanci ze souboru C:\Test.mp3
Mp3File mp3 = new Mp3File(@"C:\Test.mp3");
// Přehrajeme (ekvivalent je mp3.Status = Mp3File.Mp3FileStatus.Playing)
mp3.Play();
// Zastavíme (ekvivalent je mp3.Status = Mp3File.Mp3FileStatus.Stopped)
mp3.Stop();
// Pozastavíme (ekvivalent je mp3.Status = Mp3File.Mp3FileStatus.Paused)
// Opětovné spuštění přehrávání je pomocí metody Play()
mp3.Pause();
// Zavřeme soubor (ekvivalent je mp3.Status = Mp3File.Mp3FileStatus.Closed)
mp3.Close();
// Celkový čas určený v typu TimeSpan
label1.Text = mp3.TotalTime.ToString();
// Uplynulý čas určený v typu TimeSpan
label1.Text = mp3.ElapsedTime.ToString();
// Nastaví uplynulý čas na 10s od začátku
mp3.ElapsedTime = TimeSpan.FromSeconds(10);
// Zbývající čas určený v typu TimeSpan
label1.Text = mp3.Remaining.ToString();
// Nastaví zbývající čas na 10s od konce
mp3.Remaining= TimeSpan.FromSeconds(10);
// Přetočí na začátek
mp3.Rewind();
// Přetočí o 10s vpřed
mp3.Forwards(TimeSpan.FromSeconds(10));
// Přetočí o 10s vzad
mp3.Backwards(TimeSpan.FromSeconds(10));
// Stav mp3 souboru vyjmenovaný v enum Mp3File.Mp3FileStatus
mp3.Status  
// Jméno souboru, kterému přísluší instance třídy (pouze pro čtení)
mp3.Filename
// ID3 informace podrobněji popsaná na následující stránce
mp3.ID3

Třída dále obsahuje protected virtuální metody, které se volají vždy po akcích Close, Open, Play, Pause, Stop a po posunech v čase. Tyto virtuální metody potom volají eventy. Jedná se o stejný systém jako používají např. ovládací prvky z System.Windows.Forms.

Jména eventů jsou:

Mp3Close, Mp3Load, Mp3Play, Mp3Pause, Mp3Stop a Mp3Seek

Mp3Seek obsahuje navíc informaci o čase kam se čas posunul. Předávání se konná přes Mp3File.TimeSpanEventArgs. 

Uznávám, že jména eventů nejsou zrovna nejšťastnější, ale zase na druhou stranu se alespoň nemotají s názvy metod.

ID3 tagy

Moje třida Mp3File obsahuje podtřídu Mp3File.Mp3ID3v1, která zprostředkovává informace z tagu ID3v1.* (verze řady 1). Je navržena tak, aby se dala použít nezávisle na hlavní třídě Mp3File.

Nedá se vytvořit pomocí operátoru new, ale obsahuje statickou metodu

public static Mp3ID3v1 CreateId3v1(string filename);

,která instanci vytvoří pokud soubor obsahuje ID3v1 tag. Pokud ne, tak vrátí null.

ID3v1 obsahují tyto položky (v závorce uvádím jméno vlastnosti třídy Mp3ID3v1):

  • Jméno skladby (Title)
  • Autor (Artist)
  • Album (Album)
  • Rok vydání (Year)
  • Komentář (Comment)
  • Číslo skladby [pouze u ID3v1.1] (Track)
  • Hudebni žánr (Genre)
  • ID hudebního žánru (GenreID)

Všechny vlastnosti uvedé v závorkách jsou i pro zápis. Do souboru se změny promítnou po zavolání metody Mp3ID3v1.Update();

Co se týče hudebních žánrů, tak třída podporuje místo standardních 80ti žánrů také další, které rozšířil WinAmp. Jejich seznam, ze kterého jsem vycházel je zde.

Jak jsem již psal v přehledu metod a vlastností třídy Mp3File na předchozí stránce, k informacím o ID3 tagu se lze dostat pomocí vlastnosti Mp3File.ID3, která v případě absence ID3v1 vrací null.

Závěr

Původně jsem měl v plánu vytvořit nějaký jednoduchoučký přehrávač, který by demonstroval všechny funkce mojí třídy, ale nakonec jsem usoudil, že použití je natolik jednoduché, že to snad nebude potřeba. Pokud jste jiného názoru, napište mi do komentářů a já nějaké demo spáchám. ;)

Určitě by se třída dala vylepšit minimálně o načítaní novějších druhů ID3 tagů, ale uvidíme jestli se rozšíření dočká, protože času máme všichni málo.

Doufám, že tento můj po večerech dělaný kód si najde své příznivce a pokud ho použije alespoň jeden z vás, pak měl on i tento článek smysl. 

Děkuji za pozornost. :)