URL parsing that keeps up with how Youtube links are actually shared today, and a way to page through a channel's playlists. Additive only, so this is a drop-in upgrade from 1.3.0, with one narrow behaviour change noted under Changed.
parseVIdFromURL() now understands Shorts URLs (youtube.com/shorts/xxxx).
Reported in #79 by
[@malbero](https://github.com/malbero).parseVIdFromURL() now understands two further path forms:
youtube.com/live/xxxx, which is what live streams and the VODs they leave
behind are shared as, and the legacy embed URL youtube.com/v/xxxx, which is
still present in a lot of older content. Both accept a query string and a
trailing slash, and both work with or without www and with or without a
scheme, exactly as /embed/ and /shorts/ already did. Requested in
#85.getPlaylistsByChannelIdAdvanced($params, $pageInfo = false), which takes raw
playlists.list parameters and can return the pagination envelope alongside
the results. getPlaylistsByChannelId() returned one page and gave the caller
no way to read nextPageToken, so a channel with more playlists than a single
page holds was silently truncated; passing a pageToken through $params and
reading the next one back from info now walks the whole channel.
getPlaylistsByChannelId() is unchanged for existing callers and is now a thin
wrapper over the new method, the same way getPlaylistItemsByPlaylistId()
wraps its own ...Advanced() counterpart. Originally proposed in
#71 by
[@tyteck](https://github.com/tyteck).getPlaylistsByChannelId() now rejects a null channel id before making a
request, throwing InvalidArgumentException the way the other ...Advanced()
methods already do for their required ids. Previously the null was passed
through as an empty channelId parameter and the request was sent anyway, so
the caller paid a quota unit to have the API reject it. Any other value,
including the empty string, reaches the API exactly as before. Callers that
catch \Exception are unaffected, since InvalidArgumentException extends it.parseVIdFromURL() discarded the video id whenever a path-style URL carried a
query string, because the ?v= branch assigned null unconditionally when no
v parameter was present. youtube.com/embed/xxxx?start=30 and
youtube.com/shorts/xxxx?feature=share both threw
"The supplied URL does not look like a Youtube URL". A ?v= parameter still
takes precedence when one is actually present. Also from
#79./embed/ was matched anywhere in the URL rather than at the start of the path,
so the id was parsed from the wrong offset for URLs that merely contained the
word. Both /embed/ and /shorts/ are now matched as path prefixes, and only
the first path segment is taken as the id, so a trailing slash is no longer
included in the returned id.youtu.be/xxxx/, returned the id
with the slash still attached (xxxx/), which then produced 404s from the API
for anyone passing the result straight through. The youtu.be branch took the
whole path rather than its first segment; it now follows the same rule as the
path-prefix forms. Reported alongside
#85.Full changelog: https://github.com/madcoda/php-youtube-api/compare/v1.3.0...v1.4.0
Support for current PHP, with the PHP 7.0 floor kept intact. There are no public API removals, so this is a drop-in upgrade from 1.2.x.
Published retroactively. The
v1.3.0tag was pushed on 2026-07-27 and has been installable via Composer since then — only this releases-page entry was missing.
Madcoda\Youtube\Http\HttpClientInterface and CurlHttpClient, so the
transport can be replaced. Youtube::setHttpClient() and
Youtube::getHttpClient() expose it. This is what lets the test suite run
without a network connection or an API key.Youtube alias are
registered automatically on Laravel 5.5 and newer.tests/tools/record-fixtures.php, which re-records every fixture from live
responses in one command, and a fixture-drift test that fails when a fixture
records a field the API has stopped returning. Together these stop the
offline suite from silently asserting against a shape Youtube no longer
produces.ext-json is now declared. The library has always used json_decode().decodeSingle() passed the decoded body straight to
property_exists(). A response that is not JSON (an HTML error page, an empty
body, a truncated read) decodes to null, which is a TypeError on PHP 8.
PHP 7 only raised a warning and returned false. Any caller on PHP 8 hit a
hard crash whenever the API returned a non-JSON response.$optionalParams to
getChannelByName(), getChannelById(), getChannelsById(),
getPlaylistsByChannelId() or getActivitiesByChannelId() reached
array_merge() and raised a TypeError. Such values are now ignored.decodeList() read pageInfo, kind, etag and items without checking
them, producing five warnings per failed decode on PHP 8.page_info is now reset on every decode. A failed decode previously left the
caller reading pagination data from the previous, successful request.parse_str() was given parse_url()'s null when a URL had no query string,
deprecated on PHP 8.1+. This fired for every /embed/ URL.parseVIdFromURL() used strpos() as a boolean, so a URL starting with
youtube.com or youtu.be produced offset 0 and was rejected as invalid.
Scheme-less URLs such as youtu.be/xxxx and youtube.com/watch?v=xxxx are
now parsed correctly.$sslPath constructor argument was stored but never used as a path:
CURLOPT_CAINFO and CURLOPT_CAPATH were both hardcoded to the CA bundle
vendored in src/cert, silently replacing the system trust store with a
2013-vintage snapshot, and CURLOPT_CAPATH was given a file where it expects
a directory. The supplied path is now honoured, and a path that does not exist
raises InvalidArgumentException at construction instead of quietly using the
wrong certificates..../videos?part=id plus key=K became
.../videos?part=idkey=K. Reachable by anyone overriding $APIs or the
apis config key.[@return](https://github.com/return) docblocks were wrong. getVideosInfo(), searchVideos(),
searchChannelVideos() and getChannelsById() were all documented as
returning \StdClass when they return arrays, and neither decodeSingle()
nor decodeList() documented that they can return false.protected $defer = true, which Laravel has
ignored since 5.8 replaced it with the DeferrableProvider interface.config_path() call, which does not exist
on Lumen and made boot() fatal there.live group, excluded by
default, and run on a schedule in CI.tests/Subjects.php. The previous suite used whatever was to hand in 2013:
one of its videos has since been deleted outright, and two others turned out
to live on unofficial re-upload channels rather than the rights holders'.
Playlist tests now use a channel's uploads playlist, which cannot be deleted
while the channel exists, instead of a curated playlist that can.public const
is PHP 7.1+ syntax and would not parse on the 7.0 floor.composer.lock is no longer committed. A library should not ship one, and it
pinned the dev toolchain to PHPUnit 5.7 for everyone.src/compat.php. It claimed to alias the pre-PSR-4 Madcoda\Youtube class,
but was actually named Madcoda\compat and aliased nothing. It has been dead
since the PSR-4 move in 2016 and was loaded on every autoload dump..travis.yml. travis-ci.org shut down in 2021.YoutubeServiceProviderLaravel4. Laravel 4 has been end of life since 2015,
and the provider calls package() and bindShared(), both removed in
Laravel 5, so it cannot work on any supported framework version. It will be
removed in 2.0.src/cert/cacert.pem. This CA bundle is no longer referenced by the library
and its contents date from 2013. It will be removed in 2.0; rely on the system
trust store, or pass an explicit path to the constructor.Full changelog: https://github.com/madcoda/php-youtube-api/blob/v1.3.0/CHANGELOG.md
Merged
#76 Fixed a PHP Warning
Merge #73
Fix Laravel 5 integration
fix namespace error
Thanks @JavierMartinz and @peter279k
PSR-4 autoload
add Laravel 4 and 5 support classes, you can set the api key in the config file!
Various patches from the community
Various bug fixes and new functions contributed by the community
Stable basic wrapper
How can I help you explore Laravel packages today?