Skip to content
sk89q edited this page Aug 16, 2010 · 20 revisions

This document only concerns the newtake branch.

Introduction

When a user wants to play a video, the user first puts in a media open request. This is the built-in way of player control but it can overridden via the API so that you can use your own control mechanism. This media open request gets sent to the instance’s OpenMedia method which calls PlayX.ResolveProvider to get or guess the provider (YouTube, Vimeo, etc.). The provider converts the arguments into a handler name and its arguments. This handler name and the arguments are sent to the client and are given to the specified handler. The handler builds the HTML, CSS, and JavaScript for the browser and the client entity takes this handler result and uses it to play the media.

When a player (client-side) receives media, it will automatically start playing if PlayX is enabled. You can start or stop play using ENT:Start() and ENT:Stop() (client-side). Note that toggling whether PlayX is enabled will start or stop all players.

The API Reference can be found here:
http://sk89q.github.com/playx/docs/api/newtake/

Spawning the Player

The player’s entity is named gmod_playx and it can be spawned like any other entity. The only critical piece of information is the entity’s model because it determines the screen position. It is currently not possible to manually define a screen location.

local ent = ents.Create("gmod_playx")
ent:SetModel("models/dav0r/camera.mdl")
ent:SetPos(Vector(-124.8963, 399.7217, 300))
ent:SetAngles(Angle(90, 0, 0))
ent:Spawn()
ent:Activate()

PlayX can be embedded inside a map using Hammer as well.

Playing Media

There are two methods to playing media:

  • Using ENT:OpenMedia(), which will automatically find a provider to generate the handler information.
  • Using ENT:BeginMedia(), where you have to provide the handler and handler arguments directly.

The normal player control mechanism uses OpenMedia() but for a gamemode, it provides little control of acceptable videos. If you want finer control, you will have to generate the handler manually.

Resolving Providers

PlayX.ResolveProvider() detects the provider (or lets you force one) and returns the provider name (useful if it was detected) and handler table if given a media URI. You can thus check the returned information to see if you want to allow the media. If you do, you could pass the handler information onto ENT:BeginMedia().

local provider, result = PlayX.ResolveProvider(provider, uri, useJW)

-- No provider was detected
if provider == nil then
    return false, result -- Result is a human-readable error message 
end

ent:BeginMedia(result.Handler, result.URI, start, result.ResumeSupported,
               result.LowFramerate, result.HandlerArgs)

In the handler table that is returned is a QueryMetadata() member function that fetches whatever metadata is available for the media. The function takes two arguments: a success callback and a failure callback. The first argument passed to the success callback will be a table contain metadata information. An example member of this metadata information table is Length, which is the length of the media in seconds. This can be used to restrict videos based on length.

ENT:OpenMedia() calls the metadata query function and updates the entity’s metadata with the information using ENT:UpdateMetadata(). If you call ENT:BeginMedia() instead, the metadata information will not be provided to the entity. The following code is the code in OpenMedia() that updates the metadata information.

if result.QueryMetadata then
    result.QueryMetadata(function(data)
        if ValidEntity(self) then self:UpdateMetadata(data) end
    end,
    function(err)
        if ValidEntity(self) then self.OnMetadataError(err) end
    end)
end

Note that you cannot begin any media right after the PlayX entity was spawned because PlayX sends a usermessage when you begin media. This usermessage will arrive before the entity actually exists on the client, causing an error.

Closing Media

Call ENT:CloseMedia() to close media.

Controlling Subscribers

People who are able to view a PlayX entity are called “subscribers.” Subscribers can be added manually, but they can also be added via “auto subscription,” where users are automatically subscribed to a PlayX instance when they join or when the entity is created. Behavior by default is to subscribe everyone, but you can control this behavior with the PlayXShouldAutoSubscribe hook. You can even disable it all together by returning false for the hook.

-- Disable auto-subscription
hook.Add("PlayXShouldAutoSubscribe", "Example", function(ply, instance)
    return false
end)

To manually subscribe or unsubscribe a player, you can call the ENT:Subscribe and ENT:Unsubscribe methods on a PlayX entity. Note that manual subscription will not collide with the PlayXShouldAutoSubscribe hook because that hook is only used when a player joins the server or when a new PlayX entity is created. It is never used to “check” subscribers.

Example code to automatically subscribe or unsubscribe a user from a player when the user presses use on the entity follows:

hook.Add("PlayXUse", "Example", function(instance, activator, caller)
    if activator:IsPlayer() then
        if instance:IsSubscribed(activator) then
            instance:Unsubscribe(activator)
        else
            instance:Subscribe(activator)
        end
    end
end)

Be aware that that code does not prevent a user from spamming the use key or subscribing to multiple players at once.

If you are going to subscribe users based on distance, then you should have a buffer zone, prevent users from entering multiple trigger zones at once, and also do “smart” distance detection (mentioned in the next section).

Controlling Local Sound / Fading Sound

There are two ways to have sound fade with distance (or with any factor). Both methods are purely client-side.

  • Use the PlayXGetSoundMultiplier hook.
  • Manually call ENT:SetVolume().

The PlayXGetSoundMultiplier hook gets called every 0.1 seconds via a Think hook. However, the Think hook only gets created if PlayXGetSoundMultiplier was hooked, for performance reasons. PlayX checks to see if the hook exists during certain events such as during the start of play. It is recommended that the hook is defined from the beginning of the game if you wish to use it. The hook passes an instance and receives a volume between 0 and 100.

Alternatively, you can manually call the entity’s SetVolume() method with the volume that you desire (0 – 100). Note that you can not use both the hook and the SetVolume() method as they will override each other.

What follows is a very naive algorithm for sound fading with distance:

hook.Add("PlayXGetSoundMultiplier", "Example", function(instance)
    return 100 - LocalPlayer():GetPos():Distance(instance:GetPos()) / 100
end)

If you wish to make a smarter algorithm, especially if you are using a projector, you should do a projection of the user’s position onto the line segment between the screen and the projector entity (in the case of the projector) or between the screen and a little in front of the screen and use the distance between this point and the user. Fortunately, PlayX comes with the utility function playxlib.PointLineSegmentDistance() that does this for you. To get the two points of the line segment, you can use ENT:GetSourcePos() and ENT:GetProjectionPos(). Note that, for non-projectors, the algorithm for choosing the source position and projection position can be a bit dumb.

Be aware that repeaters are not processed or provided via the hook. The API may provide that information in the future, possibly as a second argument containing a list of repeaters.

To help debug the local sound levels, you can use playx_debug.lua found inside the contrib/ folder of PlayX. Run this client-side file via console and you will be able to see the current sound level.

Reusing the Console Commands and Tool Panel

The tool panel and bookmark manager use the console commands to play and stop media. PlayX entities are also spawned through console commands. By default, only administrators and super administrators are able to use the console commands, but you can put a stop to this by hooking PlayXIsPermitted and returning false. However, instead of doing this, you can continue to allow those functions to be used by hooking PlayXSelectInstance to choose an instance a user has control of and setting up PlayXIsPermitted to return true for this entity. The PlayXSelectInstance hook also lets you return false and an error message, and it is called before PlayXIsPermitted, meaning that you can tell the user a custom error message.

Below is simplified code from the example DarkRP TV entity. It uses both hooks to let users control their own player, but it also lets administrators control PlayX like normal.

hook.Add("PlayXSelectInstance", "PlayXTV", function(ply)
    if PlayX.IsPermitted(ply) then return end -- Allows admins to control PlayX like normal
    if not ply then return end -- This hook may be called without a provided player

    local temp = PlayX.GetInstances()
    local instances = {}

    -- We only want playx_tv entities that the player can control
    for _, instance in pairs(temp) do
        -- dt.owning_ent is how DarkRP keeps track of owners
        if instance.dt.owning_ent == ply then
            if instance:GetClass() == "playx_tv" then
                table.insert(instances, instance)
            end
        end
    end

    if #instances == 0 then
        return false, "You don't own a television! Buy one with F4."
    else
        return instances[1]
    end

    -- Get the closest TV
    local plyPos = ply:GetPos()
    table.sort(instances, function(a, b)
        return a:GetPos():Distance(plyPos) < b:GetPos():Distance(plyPos)
    end)

    return instances[1]
end)

hook.Add("PlayXIsPermitted", "PlayXTV", function(ply, instance)
    if not instance then return end
    if instance:GetClass() ~= "playx_tv" then return end
    return instance.dt.owning_ent == ply
end)

Note: Having both hooks is a bit roundabout. PlayX may use a new single hook for the console commands in the near future.

Extending gmod_playx

Rather than control PlayX via hooks, you can also create a new entity inheriting from gmod_playx. Several entity functions mirroring their hook counterparts have been added that can be overridden safely. The only consequence of overriding some of these methods is that the corresponding hooks won’t be called anymore, although you can always call the base class’s function if you do want them.

The following is an overridden function that restricts played media to YouTube videos:

function ENT:MediaOpen(provider, uri, start, forceLowFramerate, useJW, ignoreLength, result)
    if provider ~= "YouTube" then
        return false, "Only YouTube videos can be played on this TV."
    end
end

It is best if you review the entity code if you wish to take this route. Generally functions that are not pithy can be overridden without calling the respective base class function.

To get PlayX to recognize your new entity, you must add it to the “PlayXScreenClasses” list (put it in shared.lua):

list.Add("PlayXScreenClasses", "name_of_sent")

Choosing Sources for Repeaters

Repeaters use the following algorithm to choosing a source:

  • Check the client-side set source.
  • Check the server-side set source.
  • Check PlayX.GetInstance() (client-side).

The ENT:SetSource() method can be used both server-side and client-side to set the server-side and client-side sources, respectively.