/*
 *     Copyright 2015-2018 Austin Keener & Michael Ritter & Florian Spieß
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package net.dv8tion.jda.core.audio.factory;

import net.dv8tion.jda.core.audio.AudioConnection;
import net.dv8tion.jda.core.utils.JDALogger;
import org.slf4j.MDC;

import javax.annotation.CheckForNull;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.NoRouteToHostException;
import java.net.SocketException;
import java.util.concurrent.ConcurrentMap;

import static net.dv8tion.jda.core.audio.AudioConnection.OPUS_FRAME_TIME_AMOUNT;

/**
 * The default implementation of the {@link net.dv8tion.jda.core.audio.factory.IAudioSendSystem IAudioSendSystem}.
 * <br>This implementation uses a Java thread, named based on: {@link IPacketProvider#getIdentifier()} + " Sending Thread".
 */
public class DefaultSendSystem implements IAudioSendSystem
{
    private final IPacketProvider packetProvider;
    private Thread sendThread;
    private ConcurrentMap<String, String> contextMap;

    public DefaultSendSystem(IPacketProvider packetProvider)
    {
        this.packetProvider = packetProvider;
    }

    @Override
    public void setContextMap(@CheckForNull ConcurrentMap<String, String> contextMap)
    {
        this.contextMap = contextMap;
    }

    @Override
    public void start()
    {
        final DatagramSocket udpSocket = packetProvider.getUdpSocket();

        sendThread = new Thread(() ->
        {
            if (contextMap != null)
                MDC.setContextMap(contextMap);
            long lastFrameSent = System.currentTimeMillis();
            while (!udpSocket.isClosed() && !sendThread.isInterrupted())
            {
                boolean sentPacket = true;
                try
                {
                    boolean changeTalking = (System.currentTimeMillis() - lastFrameSent) > OPUS_FRAME_TIME_AMOUNT;
                    DatagramPacket packet = packetProvider.getNextPacket(changeTalking);

                    sentPacket = packet != null;
                    if (sentPacket)
                        udpSocket.send(packet);
                }
                catch (NoRouteToHostException e)
                {
                    packetProvider.onConnectionLost();
                }
                catch (SocketException e)
                {
                    //Most likely the socket has been closed due to the audio connection be closed. Next iteration will kill loop.
                }
                catch (Exception e)
                {
                    AudioConnection.LOG.error("Error while sending udp audio data", e);
                }
                finally
                {
                    long sleepTime = (OPUS_FRAME_TIME_AMOUNT) - (System.currentTimeMillis() - lastFrameSent);
                    if (sleepTime > 0)
                    {
                        try
                        {
                            Thread.sleep(sleepTime);
                        }
                        catch (InterruptedException e)
                        {
                            //We've been asked to stop.
                            Thread.currentThread().interrupt();
                        }
                    }
                    if (sentPacket)
                    {
                        if (System.currentTimeMillis() < lastFrameSent + 60) // If the sending didn't took longer than 60ms (3 times the time frame)
                        {
                            lastFrameSent += OPUS_FRAME_TIME_AMOUNT; // increase lastFrameSent
                        }
                        else
                        {
                            lastFrameSent = System.currentTimeMillis(); // else reset lastFrameSent to current time
                        }
                    }
                }
            }
        });
        sendThread.setUncaughtExceptionHandler((thread, throwable) ->
        {
            JDALogger.getLog(DefaultSendSystem.class).error("Uncaught exception in audio send thread", throwable);
            start();
        });
        sendThread.setDaemon(true);
        sendThread.setName(packetProvider.getIdentifier() + " Sending Thread");
        sendThread.setPriority((Thread.NORM_PRIORITY + Thread.MAX_PRIORITY) / 2);
        sendThread.start();
    }

    @Override
    public void shutdown()
    {
        if (sendThread != null)
            sendThread.interrupt();
    }
}

@@ -2118,24 +2118,63 @@
 meMillis();%0A
+            boolean sentPacket = true;%0A
             
@@ -2252,51 +2252,8 @@
   %7B%0A
-                boolean sentPacket = true;%0A
     
@@ -2329,16 +2329,31 @@
 alking =
+ !sentPacket %7C%7C
  (System
@@ -3781,34 +3781,71 @@
         if (
-sentPacket
+System.currentTimeMillis() %3C lastFrameSent + 60
 )%0A          
@@ -3883,61 +3883,8 @@
     
- if (System.currentTimeMillis() %3C lastFrameSent + 60)
  // 
@@ -3956,38 +3956,8 @@
 me)%0A
-                        %7B%0A    
     
@@ -4020,39 +4020,9 @@
 UNT;
- // increase lastFrameSent%0A    
+%0A
     
@@ -4031,36 +4031,32 @@
               %7D%0A
-    
                 
@@ -4076,36 +4076,32 @@
                 
-    
 %7B%0A              
@@ -4113,56 +4113,8 @@
     
-     lastFrameSent = System.currentTimeMillis();
  // 
@@ -4170,33 +4170,75 @@
                 
-%7D
+lastFrameSent = System.currentTimeMillis();
 %0A               

Fix idle issues for DefaultSendSystem