Delphi VCL TMediaPlayer: file path/name length limit

304 views Asked by At

Using Delphi 10.4 Community Edition, VCL, Windows 10 64bit, although the compiled .exe application is 32bit.

The VCL's TMediaPlayer seems to have a file path/name length limit of 128 characters. Is this really an internal limitation? Is there any way to access longer file paths/names?

I was coding a small soundPad player by using the TMediaPlayer component.

The installer I am using installs the .exe program in the user's home directory, and at the same time a few sample audio files in the program's root directory.

In this case, the path to the audio file may be quite long. For example:

C:\Users\user\AppData\Local\Programs\MySoundPlayer\ThisIsMySoundWithAVeryLongFileNameThereIsSomeCopyrightInfoAndSomeOther.wav

When trying to play such a file, TMediaPlayer will give an error message:

Exception class name = 'EMCIDeviceError'
Exception message = 'Invalid filename. Make sure the filename has 8 characters, a period, and an extension.'

I tried different lengths in the file name, and it looks like 127 is the maximum length.

So, the VCL TMediaPlayer component does not recognize file paths / names longer than 127 characters?

I tried the same code with a Delphi FMX app, and FMX's TMediaPlayer worked ok. It seems that the maximum file path and name length of the FMX TMediaPlayer is 259, which is quite sufficient.

The length 259 seem to be the limit of the File Explorer overall...

It is said that the VCL's TMediaPlayer component is starting to become obsolete, and is only involved in backward compatibility reasons. But what can replace it in the future?

So, I guess I have to move on to FMX and learn its secrets. Is VCL a receding component system?

procedure TForm1.PlayButtonClick(Sender: TObject);
var
  pathstring, playerfilename, playstring : string;
begin
  try
    pathstring := ExtractFilePath(Application.ExeName);
    playerfilename := 'ThisIsMySoundWithAVeryLongFileNameThereIsSomeCopyrightInfoAndSomeOther.wav';
    playstring := pathstring + playerfilename;
    MediaPlayer1.FileName := playstring;
    MediaPlayer1.Open;
    MediaPlayer1.Play;
  except
    on E : Exception do
    begin
      ShowMessage('Exception class name = ' + E.ClassName);
      ShowMessage('Exception message = ' + E.Message);
    end;
  end;
end;
1

There are 1 answers

0
Remy Lebeau On

Per this answer to mciSendString() won't play an audio file if path is too long:

Here, mmioOpen is called with MMIO_PARSE flag to convert file path to fully qualified file path. According to MSDN, this has a limitation:

The buffer must be large enough to hold at least 128 characters.

That is, buffer is always assumed to be 128 bytes long. For long filenames, the buffer turns out to be insufficient and mmioOpen returns error, causing mciSendCommand to think that sound file is missing and return MCIERR_FILENAME_REQUIRED.

The Invalid filename error message you are seeing is the system text for the MCIERR_FILENAME_REQUIRED error code.

The VCL's TMediaPlayer is based on MCI and internally uses mciSendCommand(), which is just the binary version of mciSendString(). They both suffer from the same problem.

The preferred fix is to either use shorter paths, or use a more modern audio API.

However, since mmioInstallIOProc() can be used to let TMediaPlayer play media files from memory instead of files, I think a similar solution could be used to play files with long file paths, since you could take over the responsibility of opening/reading/seeking a file, bypassing the path limitation of the troublesome mmioOpen(). Just replace the TResourceStream in that code with a TFileStream, and update the MMIOM_READ and MMIOM_SEEK handlers accordingly to read/seek that TFileStream.

For example (untested, might need some tweaking):

uses
  Winapi.MMSystem;

var
  ccRES: FOURCC;
  playstring: string;

function MAKEFOURCC(ch0, ch1, ch2, ch3: BYTE): FOURCC;
begin
  Result := DWORD(ch0) or (DWORD(ch1) shl 8) or (DWORD(ch2) shl 16) or (DWORD(ch3) shl 24); 
end;

function MyLongFileIOProc(lpMMIOInfo: PMMIOInfo; uMessage: UINT; lParam1, lParam2: LPARAM): LRESULT; stdcall;
var
  FStrm: TFileStream;
  NumRead: Integer;

  function GetFileStream: TFileStream;
  begin
    Move(lpMMIOInfo.adwInfo, Result, SizeOf(TFileStream));
  end;

  procedure SetFileStream(Stream: TFileStream);
  begin
    Move(Stream, lpMMIOInfo.adwInfo, SizeOf(TFileStream));
  end;

begin
  if uMessage = MMIOM_OPEN then
  begin
    try
      FStrm := TFileStream.Create(playstring, fmOpenRead or fmShareDenyWrite);
    except
      SetFileStream(nil);
      Exit(MMIOM_CANNOTOPEN);
    end;
    SetFileStream(FStrm);
    lpMMIOInfo.lDiskOffset := 0;
  end else
  begin
    FStrm := GetFileStream;
    case uMessage of
      MMIOM_CLOSE: begin
        SetFileStream(nil);
        FStrm.Free;
      end;   
      MMIOM_READ: begin
        NumRead := FStrm.Read(Pointer(lParam1)^, lParam2);
        Inc(lpMMIOInfo.lDiskOffset, NumRead);
        Exit(NumRead);
      end;
      MMIOM_SEEK: begin
        FStrm.Seek(Int64(lParam1), TSeekOrigin(lParam2));
        lpMMIOInfo.lDiskOffset := FStrm.Position;
        Exit(lpMMIOInfo.lDiskOffset);
      end;
  end;
  Exit(MMSYSERR_NOERROR);
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  ccRES := MAKEFOURCC(Ord('L'), Ord('F'), Ord('N'), Ord(' '));
  mmioInstallIOProc(ccRES, TFNMMIOProc(MyLongFileIOProc), MMIO_INSTALLPROC); 
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  mmioInstallIOProc(ccRES, nil, MMIO_REMOVEPROC);
end;

procedure TForm1.PlayButtonClick(Sender: TObject);
var
  pathstring, playerfilename : string;
begin
  try
    pathstring := ExtractFilePath(Application.ExeName);
    playerfilename := 'ThisIsMySoundWithAVeryLongFileNameThereIsSomeCopyrightInfoAndSomeOther.wav';
    playstring := pathstring + playerfilename;

    MediaPlayer1.DeviceType := dtWaveAudio;
    MediaPlayer1.FileName := 'playstring.LFN+';
    MediaPlayer1.Open;
    MediaPlayer1.Play;
  except
    on E : Exception do
    begin
      ShowMessage('Exception class name = ' + E.ClassName);
      ShowMessage('Exception message = ' + E.Message);
    end;
  end;
end;