// player.java
//
// Description
// ===========
//
// Plays a loop of a an 8 bit, 8K Mono AU file over the web page. Allows
// the viewer to start & stop the player using a play/pause button displayed
// on the page
//
// Revision History
// ================
//
// 15-Feb-1996/SBH      Very first rough hack. No error checks.
// (0.0 alpha)

// 16-Feb-1996/SBH      Added start/stop control.
// (0.1 alpha)

// 21-Feb-1996/SBH      Added loop=YES/NO (default is YES). Note, if in
// (0.2alpha)           NO mode, button does not reset when finished playing.

// 23-Feb-1996/SBH      Added Timer.class, which now means that the button
// (1.0)                will revert to a 'play' button a specified time after
//                      the sound starts playing. Ideally, this should be a few
//                      milliseconds longer than the file plays for.
//
//
//
//
// ------------------------------------------------------------
import java.applet.Applet;
import java.applet.AudioClip;
import java.awt.*;        
import java.util.Date;
import java.lang.Thread;

// ------------------------------------------------------------
// This class is used to start another thread, which will run for
// a specified number of milliseconds, before calling the parent's
// expireTimer() function.
//
// A seperate thread is used to allow the parent to continue receiving
// mouse messages whilst we implement a delay until the current play
// has completed. Ideally, this class should be more generic and just send
// an event to it's parent...but that comes later.


class timer extends Thread

  {
   private player myParent = null;  // Parent to call back when expired
   private int    iMilliSecs;       // Time to wait
   private int    iReference;       // Our reference - used to determine
                                    //  which we are if multiple timers runnig

   public timer (player _myParent, int _iMilliSecs, int _iReference )
   {
     myParent   = _myParent;     
     iMilliSecs = _iMilliSecs;   
     iReference = _iReference;   
   }//timer()

// ------------------------------------------------------------

   public void run()
     {
       // we come here when our .start() method is called.
       try
         {
           sleep(iMilliSecs);
         }
         catch (InterruptedException e)
         {
         }
         myParent.expireTimer(iReference);

         // The object will be discarded when we reach here

     }
  }//timer.class

//-----------------------------------------------------------------

public class player extends java.applet.Applet implements Runnable
{
  String    szVersion    = "player.class v1.0 (c)1996 Steve Hill 100144.3120@compuserve.com";
  Thread    Thread2;     // Our thread
  AudioClip acMySound;   // The Audio clip. Should be MONO, 8000 Samples/sec
  Graphics  gcOn;        // Graphics context (prevents flicker on redraw)


  boolean bPlaying       = true;  // Are we playing or silent?
  boolean bMouseDown     = false; // Is the button up or down?
  boolean bLoopMode      = true;  // Loop sound, or single shot?
  boolean bPlayImmediate = true;  // if true, play immediately else wait
                                  // until play pressed

  Rectangle buttonPlay = new Rectangle(0,0,20,20);    //size of button


  int iTimeToPlay;              // Length of .AU in Milliseconds
  int iNextPlayReference = 0;   // Next timer reference 
  int iPlayingRef =0;           // Reference currently being played

  public void init()
  {
     // get our graphics context...we do this to avoid flicker on paint
     gcOn = getGraphics();
  }//init()

// ------------------------------------------------------------

  public String getAppletInfo()

  //Description
  //===========
  //
  // This function is called in appletviewer if the user asks for applet info
  // it should provide details of applets origin.

  {
      return(szVersion);
  }//getAppletInfo()

// ------------------------------------------------------------

  public String[][] getParameterInfo()

  //Description
  //===========
  //
  // This function is called in appletviewer if the user asks for applet info
  // it should provide details of applets usage (ie, paramters)
  {
     String pinfo[][] =
     {
        {"copyright",  "string", szVersion},
        {"au_file",    "URL",    "URL of .AU file to play"},
        {"loop",       "YES|NO (default=YES)", "Played in continous loop?"},
        {"init",       "PLAY|PAUSE(default=PLAY)","Start condition"}, 
        {"playtime",   "integer","Approx Length of AudioClip in MilliSecs. Required if loop=NO"}
     };

      return (pinfo);
  }

// ------------------------------------------------------------

  void InitAudio()
  {
    // do we start playing immediately?

     String szInit = getParameter("init");

   if (szInit == null)
     {
       szInit = "PLAY";
     }

    if (szInit.toUpperCase().equals("PAUSE"))
    {
      bPlaying = false;
    }
    else
    {
      bPlaying = true;
    }

    // This fetches the file, and won't return until loaded (unlike image)

    acMySound = getAudioClip(getDocumentBase(), getParameter("au_file"));

    // Do we play a loop or not?

    String szLoop = getParameter("loop");
   
    if (szLoop == null)
        szLoop = "YES";

    if (!szLoop.toUpperCase().equals("NO"))
    {
      if (bPlaying)
      {
        acMySound.loop();
      }
      bLoopMode=true;
    }
    else
    {
     //if we're in loop mode, read the file playtime (in millisecs)

     String szLength = getParameter("playtime");

     //convert to an integer, default to 1 second if not specified.

     if (szLength != null)
     {
       try
       {
         iTimeToPlay = Integer.parseInt(szLength);
       }
       catch (Exception e)
       {
        iTimeToPlay = 1000;
        System.out.println("Invalid playtime specified. Defaulted to " + iTimeToPlay +" MilliSeconds.");
       }
     }
     else
     {
        System.out.println("No playtime specified. Defaulted to " + iTimeToPlay +" MilliSeconds.");
     }
        
     bLoopMode=false;
     if (bPlaying)
     {
       playSound();
     }
    }
  }//InitAudio()

// ------------------------------------------------------------

  void drawButtonEdge(boolean bUp, Rectangle myRect)
  {
    //Draws a blank button, in an up or down state.

    int iMidX,iMidY,x,y,w,h;
    x = myRect.x;
    y = myRect.y;
    w = myRect.width;
    h = myRect.height;

    //blank the middle

    gcOn.setColor(Color.lightGray);
    gcOn.fillRect(x+1,y+1,w-1,h-1);
    
    // Draw the black bit..

    gcOn.setColor(Color.black);
    gcOn.drawRect(x,y,w,h);
        
    // and the white bit

    if (!bUp)
    {
     iMidX = x;
     iMidY = y;
    }
    else
    {
     iMidX = x+w;
     iMidY = y+h;
    }

    gcOn.setColor(Color.white);
    gcOn.drawLine(x,y+h,iMidX,iMidY);
    gcOn.drawLine(x+w,y,iMidX,iMidY);
  }//drawButtonEdge()

// ------------------------------------------------------------

  void drawButtons()
  {
   // Draws the button. The symbol (play/pause) and up/down appearance
   // change dependendant on bPlaying & bMouseDown

   int x,y,off;


   drawButtonEdge(bMouseDown,buttonPlay);

   // We offset the symbol to the right and down by one pixel if the button
   // is depressed. If it's happy - no offset.

   if (bMouseDown)
   {
      off=1;
   }
   else
   {
      off=0;
   }

   // Draw different symbol pause/play as necessary..
   if (bPlaying)
   {
     drawStop(8+off,5+off);
   }
   else
   {
     drawPlay(8+off,5+off);
   }
 }//drawButtons()

// ------------------------------------------------------------

  public void expireTimer(int iReference)
  {
    // if this Timer was the one that was started when we last pressed
    // the play button, and we are still playing, then clear the bplaying
    // flag and redraw the button.

    if ((iReference == iPlayingRef) && bPlaying)
    {
      bPlaying = false;
      repaint();
    }
  }//expireTimer()

// ------------------------------------------------------------

  public void paint(Graphics g)
  {
    // pretty obvious really...    
    drawButtons();
  }//paint()

// ------------------------------------------------------------

  public void start()
  {
    // This is our main thread
    Thread2 = new Thread(this);
    Thread2.start();

  }//start()

// ------------------------------------------------------------

  public void stop()

  {
    Thread2.stop();
  }//stop()

// ------------------------------------------------------------

  void playSound()
  {
     acMySound.play();

     // start a separate thread to call back when we have finished playing

     timer timerPlay = new timer(this,iTimeToPlay,++iNextPlayReference);

     // keep a record of which play this is - it may be started/stopped several
     // times before the timer expires.

     iPlayingRef = iNextPlayReference;

     // and start the timer running..

     timerPlay.start();
  }//playSound()

// ------------------------------------------------------------

  void drawStop(int x,int y)
  {
    // draws two vertical back bars

    gcOn.setColor(Color.black);
    gcOn.fillRect(x,y,2,10);
    gcOn.fillRect(x+4,y,2,10);
  }//drawStop()

// ------------------------------------------------------------

  void drawPlay(int x,int y)
  {
    // draws a '>' play symbol
   
    gcOn.setColor(Color.black);
    gcOn.drawLine(x,y,x+3,y+5);
    gcOn.drawLine(x+1,y,x+4,y+5);
    gcOn.drawLine(x,y+10,x+3,y+5);
    gcOn.drawLine(x+1,y+10,x+4,y+5);
  }//drawPlay()

// ------------------------------------------------------------

  public boolean mouseDown(Event evt,int x, int y)
    {
      //When the mouse goes, draw the button in a 'down' position.

      if (buttonPlay.inside(x,y))
      {
        bMouseDown = true;
      }
      drawButtons();
      return (true);
    }//mouseDown()

// ------------------------------------------------------------

    public boolean mouseUp(Event evt,int x, int y)

    {
        //if we are still over the button, and it was down, then we toggle
        //play state.

        if (buttonPlay.inside(x,y) && bMouseDown)
          bPlaying = !bPlaying;

        bMouseDown = false;

        // redraw button. Symbol & up/down state have changed

        drawButtons();

        // now determine action to take

        if (bPlaying)
          if (bLoopMode)
            acMySound.loop();
          else
            playSound();
        else
          {
          acMySound.stop();

          // we rest this so that callback from timer has no effect
          iPlayingRef = 0;
          }
        return (true);
    }//mouseUp()

// ------------------------------------------------------------

    public void run()
    {
      System.out.println(szVersion);
      InitAudio();
      drawButtons();
    }//run()
}//player.class
//EOF

