Home > Articles > Programming > Java

  • Print
  • + Share This
Like this article? We recommend

Like this article? We recommend

Play Media with an Advanced Media Player

Modern media players feature slick-looking and dynamic control panels. For example, they often employ opacity, gradients, and reflections to make their control panels look high tech.

Also, it's common for a control panel to fade into view over the media whenever the mouse pointer enters the player window and disappear when the mouse pointer exits this window.

Take a look at Figure 4.

Figure 4

Figure 4 You can play/pause the media and observe its progress.

The control panel, which fades into view whenever the mouse enters the window, consists of a play/pause button and a progress bar.

A timer performs the fade by repeatedly adjusting the opacity and painting these controls. As the media plays, a blue-to-cyan gradient is painted to show that portion of the media that's already played—the progress bar's black area indicates the portion of the media that's yet to play.

Check out Listing 4 to explore this media player's source code.

Listing 4 AMP.java

// AMP (Advanced Media Player).java

import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

import java.awt.geom.GeneralPath;
import java.awt.geom.Rectangle2D;

import java.net.URI;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;

import com.sun.media.jmc.MediaProvider;

import com.sun.media.jmc.control.VideoRenderControl;

import com.sun.media.jmc.event.VideoRendererEvent;
import com.sun.media.jmc.event.VideoRendererListener;

public class AMP extends JFrame
{
  MediaProvider mp;

  public AMP (String mediaURI)
  {
   super ("AMP: "+mediaURI);
   setDefaultCloseOperation (EXIT_ON_CLOSE);

   try
   {
     mp = new MediaProvider (new URI (mediaURI));
   }
   catch (Exception e)
   {
     System.out.println ("Error opening media: "+e.toString ());
     System.exit (1);
   }

   setContentPane (new MediaPanel (mp));

   pack ();
   setVisible (true);
  }

  public static void main (final String [] args)
  {
   if (args.length != 1)
   {
     System.err.println ("usage: java AMP mediaURI");
     return;
   }

   Runnable r = new Runnable ()
          {
            public void run ()
            {
             new AMP (args [0]);
            }
          };
   EventQueue.invokeLater (r);
  }
}

class MediaPanel extends JPanel
{
  private AlphaComposite ac1 =
   AlphaComposite.getInstance (AlphaComposite.SRC_OVER, 0.1f);

  private AlphaComposite ac2 =
   AlphaComposite.getInstance (AlphaComposite.SRC_OVER, 0.3f);

  private ControlPanel cp;

  private Dimension frameSize;

  private Font font = new Font ("Arial", Font.BOLD, 16);

  private MediaProvider mp;

  private Rectangle frame = new Rectangle (0, 0, 0, 0);

  private Rectangle2D r2d = new Rectangle2D.Double (0.0, 0.0, 0.0, 0.0);

  private VideoRenderControl vrc;

  MediaPanel (MediaProvider mp)
  {
   this.mp = mp;

   cp = new ControlPanel ();

   addMouseListener (new MouseAdapter ()
            {
              public void mouseClicked (MouseEvent me)
              {
                cp.update (me.getX (), me.getY ());
              }

              public void mouseEntered (MouseEvent me)
              {
                cp.show ();
              }

              public void mouseExited (MouseEvent me)
              {
                cp.hide ();
              }
            });

   vrc = mp.getControl (VideoRenderControl.class);
   if (vrc == null)
   {
     System.err.println ("no video renderer control");
     System.exit (-1);
   }
   frameSize = vrc.getFrameSize ();
   VideoRendererListener vrl;
   vrl = new VideoRendererListener ()
      {
        public void videoFrameUpdated (VideoRendererEvent vre)
        {
          repaint ();
        }
      };
   vrc.addVideoRendererListener (vrl);
   setPreferredSize (new Dimension (3*frameSize.width/4,
                    3*frameSize.height/4));
  }

  protected void paintComponent (Graphics g)
  {
   super.paintComponent (g);

   frame.width = getWidth ();
   frame.height = getHeight ();
   vrc.paintVideoFrame (g, frame);

   double ar1 = frame.width/(double) frameSize.width;
   double ar2 = frame.height/(double) frameSize.height;

   double x, y;

   if (ar1 < ar2)
   {
     x = (frame.width-frameSize.width*ar1)/2+10.0;
     y = (frame.height-frameSize.height*ar1)/2+10.0;
   }
   else
   {
     x = (frame.width-frameSize.width*ar2)/2+10.0;
     y = (frame.height-frameSize.height*ar2)/2+10.0;
   }
   
   Graphics2D g2d = (Graphics2D) g;
   g2d.setRenderingHint (RenderingHints.KEY_ANTIALIASING,
              RenderingHints.VALUE_ANTIALIAS_ON);
   g2d.setComposite (ac1);
   g2d.setPaint (Color.black);
   r2d.setFrame (x, y, 125.0, 50.0);
   g2d.fill (r2d);
   g2d.setComposite (ac2);
   g2d.setFont (font);
   g2d.setPaint (Color.yellow);
   g2d.drawString ("javajeff.mb.ca", (int) x+10, (int) y+30);

   cp.paint (g2d);
  }

  private class ControlPanel
  {
   private final static int DELAY = 100;

   private final static double SEPARATOR = 5.0;

   private Timer showTimer;

   private float opacity = 0.0f;

   private GeneralPath gp = new GeneralPath ();

   private Control [] controls;

   ControlPanel ()
   {
     ActionListener alShow;
     alShow = new ActionListener ()
           {
             public void actionPerformed (ActionEvent ae)
             {
              opacity = opacity+0.1f;
              if (opacity >= 1.0f)
              {
                showTimer.stop ();
                opacity = 1.0f;
              }
              MediaPanel.this.repaint ();
             }
           };
     showTimer = new Timer (DELAY, alShow);

     controls = new Control [2];
     PlayPauseButton ppb = new PlayPauseButton (15.0, 15.0);
     controls [0] = ppb;
     ProgressBar pb = new ProgressBar (250.0, 12.5);
     controls [1] = pb;
   }

   void hide ()
   {
     showTimer.stop ();
     opacity = 0.0f;
     MediaPanel.this.repaint ();
   }

   void show ()
   {
     showTimer.start ();
   }

   void update (double x, double y)
   {
     if (showTimer.isRunning ())
       return;

     for (Control control: controls)
       if (control.contains (x, y))
         if (control instanceof PlayPauseButton)
         {
           PlayPauseButton ppb = (PlayPauseButton) control;

           if (mp.isPlaying ())
           {
             ppb.setButtonPlay (true);
             mp.pause ();
           }
           else
           {
             ppb.setButtonPlay (false);
             mp.play ();
           }
         }

     MediaPanel.this.repaint ();
   }

   private void paint (Graphics2D g2d)
   {
     AlphaComposite ac;
     ac = AlphaComposite.getInstance (AlphaComposite.SRC_OVER, opacity);
     g2d.setComposite (ac);

     double panelWidth = 0.0;
     double panelHeight = 0.0;
     for (Control c: controls)
     {
       panelWidth += c.getWidth ()+SEPARATOR;
       if (c.getHeight () > panelHeight)
         panelHeight = c.getHeight ();
     }

     double x = (frame.width-panelWidth)/2.0;
     double y = (frame.height-SEPARATOR-panelHeight);

     for (Control c: controls)
     {
       if (c instanceof ProgressBar)
         c.setPosition (x, y+1.5);
       else
         c.setPosition (x, y);
       c.paint (g2d);
       x += c.getWidth ()+SEPARATOR;
     }
   }

   private abstract class Control
   {
     protected double x, y, width, height;

     Control (double width, double height)
     {
      this.width = width;
      this.height = height;
     }

     abstract void paint (Graphics2D g2d);

     boolean contains (double x, double y)
     {
      if (x < this.x || x >= this.x+width)
        return false;

      if (y < this.y || y >= this.y+height)
        return false;

      return true;
     }

     double getHeight ()
     {
      return height;
     }

     double getWidth ()
     {
      return width;
     }

     void setPosition (double x, double y)
     {
      this.x = x;
      this.y = y;
     }
   }

   private class PlayPauseButton extends Control
   {
     private boolean isPlay = true;

     PlayPauseButton (double width, double height)
     {
      super (width, height);
     }

     boolean isButtonPlay ()
     {
      return isPlay;
     }

     void setButtonPlay (boolean isPlay)
     {
      this.isPlay = isPlay;
     }

     void paint (Graphics2D g2d)
     {
      g2d.setColor (Color.black);

      gp.reset ();  

      if (isPlay)
      {
        gp.moveTo (x, y);
        gp.lineTo (x, y+height);
        gp.lineTo (x+width, y+height/2.0);
        gp.lineTo (x, y);
      }
      else
      {
        gp.moveTo (x, y);
        gp.lineTo (x, y+height);
        gp.lineTo (x+width/3.0, y+height);
        gp.lineTo (x+width/3.0, y);
        gp.lineTo (x, y);

        gp.moveTo (x+2.0*width/3.0, y);
        gp.lineTo (x+2.0*width/3.0, y+height);
        gp.lineTo (x+width, y+height);
        gp.lineTo (x+width, y);
        gp.lineTo (x+2.0*width/3.0, y);
      }

      g2d.fill (gp);

      g2d.setColor (Color.white);

      g2d.draw (gp);
     }
   }

   private class ProgressBar extends Control
   {
     private final static int DELAY = 500;

     ProgressBar (final double width, double height)
     {
      super (width, height);

      ActionListener alUpdate;
      alUpdate = new ActionListener ()
              {
               public void actionPerformed (ActionEvent ae)
               {
                 MediaPanel.this.repaint ();
               }
              };
      new Timer (DELAY, alUpdate).start ();
     }

     void paint (Graphics2D g2d)
     {
      g2d.setColor (Color.black);

      gp.reset ();
      gp.moveTo (x, y);
      gp.lineTo (x+width, y);
      gp.lineTo (x+width, y+height);
      gp.lineTo (x, y+height);
      gp.lineTo (x, y);

      g2d.fill (gp);

      double ratio = mp.getMediaTime ()/mp.getDuration ();
      double widthLimit = width*ratio;

      g2d.setPaint (new GradientPaint ((float) x, (float) y,
                       Color.blue,
                       (float) (x+widthLimit),
                       (float) y, Color.cyan));

      gp.reset ();
      gp.moveTo (x, y);
      gp.lineTo (x+widthLimit, y);
      gp.lineTo (x+widthLimit, y+height);
      gp.lineTo (x, y+height);
      gp.lineTo (x, y);

      g2d.fill (gp);

      g2d.setColor (Color.white);

      gp.reset ();
      gp.moveTo (x, y);
      gp.lineTo (x+width, y);
      gp.lineTo (x+width, y+height);
      gp.lineTo (x, y+height);
      gp.lineTo (x, y);

      g2d.draw (gp);
     }
   }
  }
}

Listing 4 is largely an extension of Listing 3. The biggest difference between these listings is the extended MediaPanel class with its nested ControlPanel class.

MediaPanel interacts with ControlPanel via the latter class's show(), hide(), and update() methods to show/hide the control panel and respond to a play/pause media request.

ControlPanel's constructor creates an array of controls whose classes subclass the nested (and abstract) Control class.

Although I provided classes only for a play/pause button and a progress bar, you could easily introduce additional control classes—a speaker control to mute/unmute the audio, for example.

After the media panel paints the next video frame followed by the (previously discussed) overlay, it invokes ControlPanel's paint() method, giving the panel a chance to specify its current opacity, and to position and paint its controls (by invoking each Control instance's paint() method).

This listing reveals a problem with ProgressBar's paint() method: The method continually creates java.awt.GradientPaint objects.

Because excessive object creation can lead to annoying garbage collection pauses that disrupt media playback, you'll probably want to solve this problem by creating your own gradient.

The following Windows-oriented articles will help you get started:

The second article features a program that creates a custom gradient for its window's background, and also reflects an image over the gradient.

Because reflections are popular with media players, consider introducing this effect to Listing 4. For help in creating reflections, check out the JH Labs article, Fun with Java2D—Java Reflections.

  • + Share This
  • 🔖 Save To Your Account