Самописная мозаика (система мониторинга IPTV)

В данной статье я приведу рабочий пример реализации системы мозаичного мониторинга ТВ-каналов - это когда много ТВ каналов одновременно в маленьких окошках выводятся на один большой монитор.

В данной статье я приведу рабочий пример реализации системы мозаичного мониторинга ТВ-каналов - это когда много ТВ каналов одновременно в маленьких окошках выводятся на один большой монитор.

Профессиональные системы подобного типа стоят бешеных денег (но они правда того стоят - как правило они не просто делают такую мозаичную картинку - они дают еще и много нужной и важной аналитической информации о потоках, о качестве, потерях, ошибках и т.п. Но иногда достаточно просто смотреть сразу несколько каналов :) и решить подобную задачу подручными средствами и бесплатно - вполне реально.

Я решил использовать для этого старый добрый Delphi 7 и VLC, точнее библиотеку для libvlc.dll (которая идет в составе дистрибутива VLC player'а) и интерфейс для этой dll-ки - PasLibVlc. На момент написания статьи проект располагался по адресу http://sourceforge.net/projects/paslibvlc/ . Этот интерфейс дает доступ к функциям из библиотеки libvlc.dll который и позволил мне соорудить свой, на самом деле, по сути своей, мозаичный плеер, с зачаточными функциями сбора статистики. Моя программа работает в двух режимах - первый режим - если в плейлисте количество каналов совпадает с количеством установленных экранов, она статично отображает на экране заданные каналы и при этом позволяет посмотреть накопленную статистику по кадрам для одного из каналов. В случае каких либо ошибок - обрамляет красным канал с ошибками. И второй режим - в заданном количестве окошек она пробегает по кругу по всему заданному плейлисту. Плейлист задается как обычный .m3u

Ну пришло время кода :), и немножко с комментариями. В конце статьи будет ссылка на архив где будут все исходники и бинарник программы (разумеется необходима еще установленная библиотека libvlc.dll)

основной модуль unit1.pas Download file Unit1.pas

unit Unit1;
 
interface
 
uses
  Windows, Messages, SysUtils, StrUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ExtCtrls, PasLibVlcPlayerUnit, PasLibVlcUnit;
 
type
  TForm1 = class(TForm)
    Timer1: TTimer;
    Panel1: TPanel;
    Label1: TLabel;
    Timer2: TTimer;
    Edit1: TEdit;
    Label2: TLabel;
    Button1: TButton;
    Label3: TLabel;
    Label4: TLabel;
    Label5: TLabel;
    Label6: TLabel;
    Label7: TLabel;
    Label8: TLabel;
    Label9: TLabel;
    Label10: TLabel;
    Label11: TLabel;
    Label12: TLabel;
    Label13: TLabel;
    Label14: TLabel;
    Label15: TLabel;
    Label16: TLabel;
    Label17: TLabel;
    Label18: TLabel;
    procedure FormCreate(Sender: TObject);
    procedure Timer1Timer(Sender: TObject);
    procedure Timer2Timer(Sender: TObject);
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;
 
var
  Form1: TForm1;
  State_on: boolean= false;
  VlcPlayer: array[1..40] of TPasLibVlcPlayer;
  VP_x,VP_y,VP_n, Pause_switch, Num_Active: integer;
  TV_URLs, TV_Names: TStrings;
  Timer_first_finish: boolean;
  URL_Count, Last_URL, Last_MNTR, First_URL: integer;
  mntr: array[1..40] of record
    active: boolean;
    m_x,m_y: integer;
    url: string;
  end;
  urls: array[1..250] of record
    mntr_idx: integer;
    url: string;
    name: string;
  end;
  vlc_stats, prev_vlc_stats: array[1..40] of libvlc_media_stats_t;
 
 
implementation
 
uses unit2;
{$R *.dfm}
 
procedure ParseM3UPlayList(M3U: string; var Names: TStrings; var URLs: TStrings);
var
  TMPstr: TStrings;
  i: integer;
  s: string;
begin
  TMPstr := TStringList.Create;
  TMPstr.NameValueSeparator := ',';
 
  if ( M3U[1]=#$ef) and (M3U[2]=#$bb) and (M3U[3]=#$bf)
     then TMPstr.Text := UTF8ToAnsi(M3U)
     else TMPstr.Text := M3U;
 
  Names.Clear;
  URLs.Clear;
  for i := 0 to TMPstr.Count - 1 do begin
    s := TMPstr.Strings[i];
    if s = '' then continue;
    if AnsiContainsText(s,'#extm3u') then continue;
    if not AnsiContainsText(s,'#extinf') then begin
       URLs.Append(s);
    end
    else begin
      Names.Append(Trim(TMPstr.ValueFromIndex[i]));
    end;
  end;
end;
 
 
procedure TForm1.FormCreate(Sender: TObject);
var
  i, j: integer;
  TmpStr: TStrings;
begin
  VP_n := 0;
  Form2 := TForm2.Create(Self);
  if Form2.ShowModal = mrOk then begin
    VP_x := StrToInt(Form2.Edit1.Text);
    VP_y := StrToInt(Form2.Edit2.Text);
    Num_Active := VP_y * VP_x;
    if Num_Active > 40 then begin
      ShowMessage('Слишком много (максимум 40) мониторов: '+IntToStr(Num_Active));
      Halt;
    end;
    ShowMessage('Кол-во активных мониторов принудительно установлено: '+IntToStr(Num_Active));
 
    Pause_Switch := StrToInt(Form2.Edit5.Text);
    for i := 1 to VP_y do begin
      for j := 1 to VP_x do begin
        inc(VP_n);
        VlcPlayer[VP_n] := TPasLibVlcPlayer.CreateParented(Form1.Handle);
        VlcPlayer[VP_n].Parent := Form1;
        VlcPlayer[VP_n].Top := 5+245*(i-1);
        VlcPlayer[VP_n].Left := 5+325*(j-1);
        VlcPlayer[VP_n].Visible := true;
        mntr[VP_n].m_x := j;
        mntr[VP_n].m_y := i;
      end;
    end;
    ClientWidth := VP_x * 325 - 5;
    ClientHeight := VP_y * 245 + 50;
 
    TV_URLs := TStringList.Create;
    TV_Names := TStringList.Create;
    TmpStr := TStringList.Create;
 
    TmpStr.Clear;
    if FileExists(Form2.Edit4.Text) then begin
      TmpStr.LoadFromFile(Form2.Edit4.Text);
      TV_URLs.Clear;
      TV_Names.Clear;
      ParseM3UPlayList(TmpStr.Text, TV_Names, TV_URLs);
    end
    else begin
      ShowMessage('Плейлист не найден :(');
      Halt;
    end;
  end;
  URL_Count := TV_URLs.Count;
  Last_URL := 0;
  First_URL := 0;
  Last_MNTR := 0;
  for i := 1 to URL_Count do begin
    urls[i].mntr_idx := 0;
    urls[i].url := TV_URLs.Strings[i-1];
    urls[i].name := TV_Names.Strings[i-1];
  end;
 
  Timer1.Enabled := true;
  Timer1.Interval := 1000*Pause_switch;
  Timer_first_finish := false;
  Timer1Timer(Sender);
 
  for i := 1 to VP_n do begin
    mntr[i].url := '';
    mntr[i].active := false;
  end
end;
 
procedure TForm1.Timer1Timer(Sender: TObject);
var
  i, num_a: integer;
begin
  if Last_MNTR > 0 then begin
     Form1.Canvas.Pen.Color := clBtnFace;
     Form1.Canvas.Pen.Width := 5;
     Form1.Canvas.Rectangle(VlcPlayer[Last_MNTR].Left - 3, VlcPlayer[Last_MNTR].Top - 3,VlcPlayer[Last_MNTR].Left + 323, VlcPlayer[Last_MNTR].Top + 243);
  end;
 
// предполагаем что урлов больше чем мониторов
  if URL_Count > VP_n then begin
    num_a := 0;
// проходим по всем урлам - проверяем запущены ли все у нас мониторы или еще нет!
    for i := 1 to VP_n do if mntr[i].active then inc(num_a);
 
    // активных мониторов меньше ограничения - ищем первый свободный и запускаем там канал Last_URL+1
    if num_a < Num_Active then begin
       inc(Last_MNTR);
       if Last_MNTR > VP_n then Last_MNTR := 1;
       inc(Last_URL);
       if Last_URL > URL_Count then Last_URL := 1;
 
       mntr[Last_MNTR].active := true;
       mntr[Last_MNTR].url := urls[Last_URL].url;
       urls[Last_URL].mntr_idx := Last_MNTR;
       if Last_MNTR > 1 then VlcPlayer[Last_MNTR].SetAudioVolume(0)
                        else VlcPlayer[Last_MNTR].SetAudioVolume(50);
 
       Label1.Caption := 'запускаю: '+ IntToStr(Last_URL)+'. '+ urls[Last_URL].name + ' ('+ urls[Last_URL].url + ') в '+IntToStr(Last_MNTR)+' окне';
       Form1.Canvas.Pen.Color := clLime;
       Form1.Canvas.Pen.Width := 5;
       Form1.Canvas.Rectangle(VlcPlayer[Last_MNTR].Left - 3, VlcPlayer[Last_MNTR].Top - 3,VlcPlayer[Last_MNTR].Left + 323, VlcPlayer[Last_MNTR].Top + 243);
 
       VlcPlayer[Last_MNTR].Play(mntr[Last_MNTR].url);
       EXIT;
    end
    //все кол-во разрешенных мониторов уже запущено
    else begin
      // Если при этом разрешенных мониторов (Num_Active) столько же сколько всего доступных мониторов (VP_n)
      if Num_Active = VP_n then begin
       inc(Last_MNTR);
       if Last_MNTR > VP_n then Last_MNTR := 1;
       inc(Last_URL);
       if Last_URL > URL_Count then Last_URL := 1;
 
       mntr[Last_MNTR].active := true;
       mntr[Last_MNTR].url := urls[Last_URL].url;
       urls[Last_URL].mntr_idx := Last_MNTR;
       if Last_MNTR > 1 then VlcPlayer[Last_MNTR].SetAudioVolume(0)
                        else VlcPlayer[Last_MNTR].SetAudioVolume(50);
       Label1.Caption := 'запускаю: '+IntToStr(Last_URL)+'. '+ urls[Last_URL].name + ' ('+ urls[Last_URL].url + ') в '+IntToStr(Last_MNTR)+' окне';
       Form1.Canvas.Pen.Color := clLime;
       Form1.Canvas.Pen.Width := 5;
       Form1.Canvas.Rectangle(VlcPlayer[Last_MNTR].Left - 3, VlcPlayer[Last_MNTR].Top - 3,VlcPlayer[Last_MNTR].Left + 323, VlcPlayer[Last_MNTR].Top + 243);
 
       VlcPlayer[Last_MNTR].Play(mntr[Last_MNTR].url);
       EXIT;
      end
      else begin
       for i := 1 to URL_Count do if urls[i].mntr_idx = Last_MNTR then urls[i].mntr_idx := 0;
       inc(Last_MNTR);
       if Last_MNTR > VP_n then Last_MNTR := 1;
       inc(Last_URL);
       if Last_URL > URL_Count then Last_URL := 1;
 
       if Last_MNTR - Num_Active < 0 then begin
         mntr[Last_MNTR+VP_n-Num_Active].active := false;
         VlcPlayer[Last_MNTR+VP_n-Num_Active].Play('zastavka.ps');
       end
       else begin
         mntr[Last_MNTR-Num_Active].active := false;
         VlcPlayer[Last_MNTR-Num_Active].Play('zastavka.ps');
       end;
 
       mntr[Last_MNTR].active := true;
       mntr[Last_MNTR].url := urls[Last_URL].url;
       urls[Last_URL].mntr_idx := Last_MNTR;
       Label1.Caption := 'запускаю: '+IntToStr(Last_URL)+'. '+ urls[Last_URL].name + ' ('+ urls[Last_URL].url + ') в '+IntToStr(Last_MNTR)+' окне';
       Form1.Canvas.Pen.Color := clLime;
       Form1.Canvas.Pen.Width := 5;
       Form1.Canvas.Rectangle(VlcPlayer[Last_MNTR].Left - 3, VlcPlayer[Last_MNTR].Top - 3,VlcPlayer[Last_MNTR].Left + 323, VlcPlayer[Last_MNTR].Top + 243);
 
       VlcPlayer[Last_MNTR].Play(mntr[Last_MNTR].url);
      end;
 
    end;
  end
  else if URL_Count = VP_n then begin // отдельный случай - чисто для мониторинга
  // предполагаем что урлов стока же скока и мониторов - поэтому тупо все быстро запускаем - и ищем ошибки в потоках
    VlcPlayer[1].SetAudioVolume(50);
    VlcPlayer[1].Play(urls[1].url);
    // звук будет тока в первом окошке
    Sleep(2000);
    for i := 2 to URL_Count do begin
       VlcPlayer[i].SetAudioVolume(0);
       VlcPlayer[i].Play(urls[i].url);
       Sleep(300);
    end;
    for i := 1 to URL_Count do prev_vlc_stats[i] := VlcPlayer[i].GetStats;
 
    Timer1.Enabled := false;
 
    Edit1.Visible := true;
    Button1.Visible := true;
    for i := 2 to 18 do (FindComponent('Label'+IntToStr(i)) as TLabel).Visible := true;
 
    Timer2.Enabled := true;
  end
end;
 
 
procedure TForm1.Timer2Timer(Sender: TObject);
var
//  stat:libvlc_media_stats_t;
  i: integer;
begin
  for i := 1 to URL_Count do begin
    vlc_stats[i] := VlcPlayer[i].GetStats;
    if (vlc_stats[i].i_demux_corrupted > prev_vlc_stats[i].i_demux_corrupted) or
       (vlc_stats[i].i_demux_discontinuity > prev_vlc_stats[i].i_demux_discontinuity) or
       (vlc_stats[i].i_lost_pictures > prev_vlc_stats[i].i_lost_pictures) or
       (vlc_stats[i].i_lost_abuffers > prev_vlc_stats[i].i_lost_abuffers) then Form1.Canvas.Pen.Color := clRed;
 
    Form1.Canvas.Pen.Width := 3;
    Form1.Canvas.Rectangle(VlcPlayer[i].Left - 2, VlcPlayer[i].Top - 2,VlcPlayer[i].Left + 322, VlcPlayer[i].Top + 242);
    prev_vlc_stats[i] := vlc_stats[i];
  end;
  i := StrToInt(Label18.Caption);
  Label3.Caption := IntToStr(vlc_stats[i].i_read_bytes)+' Read bytes';
  Label4.Caption := IntToStr(vlc_stats[i].i_demux_read_bytes)+' Demux read bytes';
  Label5.Caption := IntToStr(vlc_stats[i].i_demux_corrupted)+' Demux corrupted';
  Label6.Caption := IntToStr(vlc_stats[i].i_demux_discontinuity)+' Demux discontinuity';
  Label7.Caption := IntToStr(vlc_stats[i].i_decoded_video)+' Decoded video';
  Label8.Caption := IntToStr(vlc_stats[i].i_decoded_audio)+' Decoded audio';
  Label9.Caption := IntToStr(vlc_stats[i].i_displayed_pictures)+' Displayed pictures';
  Label10.Caption := IntToStr(vlc_stats[i].i_lost_pictures)+' Lost pictures';
  Label11.Caption := IntToStr(vlc_stats[i].i_played_abuffers)+' Played ABuffers';
  Label12.Caption := IntToStr(vlc_stats[i].i_lost_abuffers)+' Lost ABuffers';
  Label13.Caption := IntToStr(vlc_stats[i].i_sent_packets)+' Sent packets';
  Label14.Caption := IntToStr(vlc_stats[i].i_sent_bytes)+' Sent bytes';
  Label15.Caption := FloatToStr(vlc_stats[i].f_input_bitrate)+' Input bitrate';
  Label16.Caption := FloatToStr(vlc_stats[i].f_demux_bitrate)+' Demux bitrate';
  Label17.Caption := FloatToStr(vlc_stats[i].f_send_bitrate)+' Send bitrate';
end;
 
procedure TForm1.Button1Click(Sender: TObject);
var
  i: integer;
begin
  i := StrToInt(Edit1.Text);
  if (i > 0) and (i <= URL_Count) then begin
    VlcPlayer[StrToInt(Label18.Caption)].SetAudioVolume(0);
    Label18.Caption := IntToStr(i);
    VlcPlayer[i].SetAudioVolume(50);
  end;
end;
 
end.
 

модуль unit2.pas с показом настроек - совсем простенький

Download file Unit2.pas
unit Unit2;
 
interface
 
uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, Buttons;
 
type
  TForm2 = class(TForm)
    Edit1: TEdit;
    Edit2: TEdit;
    Label1: TLabel;
    Label2: TLabel;
    Edit3: TEdit;
    Label3: TLabel;
    Edit4: TEdit;
    BitBtn1: TBitBtn;
    Label4: TLabel;
    Button1: TButton;
    Edit5: TEdit;
    Label5: TLabel;
    OpenDialog1: TOpenDialog;
    procedure BitBtn1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;
 
var
  Form2: TForm2;
 
implementation
 
{$R *.dfm}
 
procedure TForm2.BitBtn1Click(Sender: TObject);
begin
  if OpenDialog1.Execute then Edit4.Text := OpenDialog1.FileName;
end;
 
end.
 

описание формы для unit1.dfm

Download file Unit1.dfm
object Form1: TForm1
  Left = 366
  Top = 257
  Width = 679
  Height = 445
  Caption = 'Moza IC by IbZ(c)'
  Color = clBtnFace
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'MS Sans Serif'
  Font.Style = []
  OldCreateOrder = False
  Position = poScreenCenter
  OnCreate = FormCreate
  PixelsPerInch = 96
  TextHeight = 13
  object Panel1: TPanel
    Left = 0
    Top = 368
    Width = 671
    Height = 50
    Align = alBottom
    TabOrder = 0
    object Label1: TLabel
      Left = 8
      Top = 8
      Width = 7
      Height = 24
      Font.Charset = DEFAULT_CHARSET
      Font.Color = clNavy
      Font.Height = -19
      Font.Name = 'MS Sans Serif'
      Font.Style = [fsBold]
      ParentFont = False
    end
    object Label2: TLabel
      Left = 32
      Top = 4
      Width = 140
      Height = 13
      Caption = #8470' '#1082#1072#1085#1072#1083#1072' '#1076#1083#1103' '#1084#1086#1085#1080#1090#1086#1088#1080#1085#1075#1072
      Visible = False
    end
    object Label3: TLabel
      Left = 616
      Top = 0
      Width = 54
      Height = 13
      Caption = 'Read bytes'
      Visible = False
    end
    object Label4: TLabel
      Left = 616
      Top = 24
      Width = 85
      Height = 13
      Caption = 'Demux read bytes'
      Visible = False
    end
    object Label5: TLabel
      Left = 472
      Top = 0
      Width = 81
      Height = 13
      Caption = 'Demux corrupted'
      Visible = False
    end
    object Label6: TLabel
      Left = 472
      Top = 12
      Width = 94
      Height = 13
      Caption = 'Demux discontinuity'
      Visible = False
    end
    object Label7: TLabel
      Left = 336
      Top = 0
      Width = 73
      Height = 13
      Caption = 'Decoded video'
      Visible = False
    end
    object Label8: TLabel
      Left = 336
      Top = 24
      Width = 73
      Height = 13
      Caption = 'Decoded audio'
      Visible = False
    end
    object Label9: TLabel
      Left = 336
      Top = 12
      Width = 86
      Height = 13
      Caption = 'Displayed pictures'
      Visible = False
    end
    object Label10: TLabel
      Left = 472
      Top = 24
      Width = 60
      Height = 13
      Caption = 'Lost pictures'
      Visible = False
    end
    object Label11: TLabel
      Left = 336
      Top = 36
      Width = 75
      Height = 13
      Caption = 'Played ABuffers'
      Visible = False
    end
    object Label12: TLabel
      Left = 472
      Top = 36
      Width = 63
      Height = 13
      Caption = 'Lost ABuffers'
      Visible = False
    end
    object Label13: TLabel
      Left = 240
      Top = 8
      Width = 63
      Height = 13
      Caption = 'Sent packets'
      Visible = False
    end
    object Label14: TLabel
      Left = 240
      Top = 20
      Width = 53
      Height = 13
      Caption = 'Send bytes'
      Visible = False
    end
    object Label15: TLabel
      Left = 616
      Top = 12
      Width = 56
      Height = 13
      Caption = 'Input bitrate'
      Visible = False
    end
    object Label16: TLabel
      Left = 616
      Top = 36
      Width = 65
      Height = 13
      Caption = 'Demux bitrate'
      Visible = False
    end
    object Label17: TLabel
      Left = 240
      Top = 32
      Width = 57
      Height = 13
      Caption = 'Send bitrate'
      Visible = False
    end
    object Label18: TLabel
      Left = 64
      Top = 24
      Width = 6
      Height = 13
      Caption = '1'
      Visible = False
    end
    object Edit1: TEdit
      Left = 32
      Top = 20
      Width = 25
      Height = 21
      TabOrder = 0
      Text = '1'
      Visible = False
    end
    object Button1: TButton
      Left = 88
      Top = 20
      Width = 81
      Height = 25
      Caption = #1052#1086#1085#1080#1090#1086#1088#1080#1090#1100'!'
      TabOrder = 1
      Visible = False
      OnClick = Button1Click
    end
  end
  object Timer1: TTimer
    Enabled = False
    OnTimer = Timer1Timer
    Left = 88
    Top = 112
  end
  object Timer2: TTimer
    Enabled = False
    OnTimer = Timer2Timer
    Left = 128
    Top = 112
  end
end
 

описание формы для unit2.dfm

Download file Unit2.dfm
object Form2: TForm2
  Left = 519
  Top = 337
  Width = 264
  Height = 257
  Caption = #1053#1072#1089#1090#1088#1086#1077#1095#1082#1080'...'
  Color = clBtnFace
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'MS Sans Serif'
  Font.Style = []
  OldCreateOrder = False
  PixelsPerInch = 96
  TextHeight = 13
  object Label1: TLabel
    Left = 8
    Top = 12
    Width = 117
    Height = 13
    Caption = #1050#1086#1083#1080#1095#1077#1089#1090#1074#1086' '#1084#1086#1085#1080#1090#1086#1088#1086#1074
  end
  object Label2: TLabel
    Left = 200
    Top = 12
    Width = 9
    Height = 13
    Caption = 'X'
    Font.Charset = DEFAULT_CHARSET
    Font.Color = clWindowText
    Font.Height = -11
    Font.Name = 'MS Sans Serif'
    Font.Style = [fsBold]
    ParentFont = False
  end
  object Label3: TLabel
    Left = 8
    Top = 44
    Width = 200
    Height = 13
    Caption = #1054#1076#1085#1086#1074#1088#1077#1084#1077#1085#1085#1086' '#1088#1072#1073#1086#1090#1072#1102#1097#1080#1093' '#1084#1086#1085#1080#1090#1086#1088#1086#1074
  end
  object Label4: TLabel
    Left = 8
    Top = 112
    Width = 191
    Height = 13
    Caption = #1054#1090#1082#1088#1099#1090#1100' '#1092#1072#1081#1083' '#1089' '#1087#1083#1077#1081#1083#1080#1089#1090#1086#1084' '#1082#1072#1085#1072#1083#1086#1074
  end
  object Label5: TLabel
    Left = 8
    Top = 76
    Width = 194
    Height = 13
    Caption = #1055#1072#1091#1079#1072' '#1084#1077#1078#1076#1091' '#1079#1072#1087#1091#1089#1082#1086#1084' '#1082#1072#1085#1072#1083#1086#1074' ('#1089#1077#1082'.)'
  end
  object Edit1: TEdit
    Left = 160
    Top = 8
    Width = 33
    Height = 21
    TabOrder = 0
    Text = '3'
  end
  object Edit2: TEdit
    Left = 216
    Top = 8
    Width = 33
    Height = 21
    TabOrder = 1
    Text = '3'
  end
  object Edit3: TEdit
    Left = 216
    Top = 40
    Width = 33
    Height = 21
    TabOrder = 2
    Text = '5'
  end
  object Edit4: TEdit
    Left = 8
    Top = 128
    Width = 209
    Height = 21
    TabOrder = 3
    Text = 'playlist.m3u'
  end
  object BitBtn1: TBitBtn
    Left = 224
    Top = 128
    Width = 25
    Height = 25
    TabOrder = 4
    OnClick = BitBtn1Click
    Glyph.Data = {
      76010000424D7601000000000000760000002800000020000000100000000100
      04000000000000010000120B0000120B00001000000000000000000000000000
      800000800000008080008000000080008000808000007F7F7F00BFBFBF000000
      FF0000FF000000FFFF00FF000000FF00FF00FFFF0000FFFFFF00555555555555
      55555555FFFFFFFFFF55555000000000055555577777777775F55500B8B8B8B8
      B05555775F555555575F550F0B8B8B8B8B05557F75F555555575550BF0B8B8B8
      B8B0557F575FFFFFFFF7550FBF0000000000557F557777777777500BFBFBFBFB
      0555577F555555557F550B0FBFBFBFBF05557F7F555555FF75550F0BFBFBF000
      55557F75F555577755550BF0BFBF0B0555557F575FFF757F55550FB700007F05
      55557F557777557F55550BFBFBFBFB0555557F555555557F55550FBFBFBFBF05
      55557FFFFFFFFF7555550000000000555555777777777755555550FBFB055555
      5555575FFF755555555557000075555555555577775555555555}
    NumGlyphs = 2
  end
  object Button1: TButton
    Left = 8
    Top = 168
    Width = 241
    Height = 57
    Caption = #1055' '#1040' '#1045' '#1061' '#1040' '#1051' '#1048'   !!!'
    Default = True
    Font.Charset = ANSI_CHARSET
    Font.Color = clWindowText
    Font.Height = -21
    Font.Name = 'Arial'
    Font.Style = [fsBold]
    ModalResult = 1
    ParentFont = False
    TabOrder = 5
  end
  object Edit5: TEdit
    Left = 216
    Top = 72
    Width = 33
    Height = 21
    TabOrder = 6
    Text = '10'
  end
  object OpenDialog1: TOpenDialog
    Filter = 'M3U|*.m3u'
    Left = 152
    Top = 136
  end
end
 

Ссылка на архив с исходниками, скомпилированной программой и примером плейлиста Download file MozaIC.zip